Skip to content

Let PR validation grade a failing file instead of dying on it - #1392

Merged
andrewboudreau merged 1 commit into
mainfrom
fix/pr-linkcheck-draft-nameerror
Aug 11, 2026
Merged

Let PR validation grade a failing file instead of dying on it#1392
andrewboudreau merged 1 commit into
mainfrom
fix/pr-linkcheck-draft-nameerror

Conversation

@andrewboudreau

Copy link
Copy Markdown
Collaborator

The gate dies exactly when it has something to say

Since #1367 the PR validator has been unable to report on any PR containing a
non-reproducing file. The draft-banner rule reads the module under a name the import
does not bind:

import asm_policy as AP          # line 49
...
if w == "NO-REPRO" and asm_policy.has_draft_banner(text):   # line 342

and short-circuits, so the line is only evaluated once a file grades NO-REPRO
and then it raises NameError in the middle of the classification loop. The worker
reports

worker error: pr_linkcheck exited 1 without a report

with no verdict for any file, including the ones it had already graded. #1390 is
the first PR to hit it.

What it was hiding

Reproduced locally against #1390's head with the fix applied — 310 files:

verdict count
VERIFIED 305
BLIND 3
DRAFT 1
NO-SYM 1

The crash file is src/_ZN3MrI13InitResourcesEv.cpp, which sorts immediately after
src/_ZN3Amp8BehaviorEv.cpp — the last line the validator printed before dying. It
carries a // NONMATCHING banner, so the correct verdict is DRAFT, a pass. The
rule was written for exactly this file and had never once run.

The NO-SYM is real and pre-existing, not from #1390:
src/_ZN6Player16CleanupResourcesEv.cpp does not compile on clean main either
(identical error, and the PR does not touch it). That is #1367's NO-SYM category doing
its job on the first PR whose fan-out reached the file. Fixed separately in
fix/player-cleanupresources-compiles.

The fix

One word — plus the reason it survived. The rule is only reachable when a file
fails, which is the one path a green CI run never exercises, so sitting inline in
main() it was untestable and untested. The two source-text overrides move into
source_policy(worst, text) and tools/test_pr_linkcheck_verdict.py drives them
directly, including the collision the old inline comment asserted could not happen (a
bannered transcription grades DRAFT, not RAW-ASM).

Behaviour is otherwise unchanged: the extracted function preserves the original order,
and a draft banner still does not excuse WRONG.

Verified

  • tools/test_pr_linkcheck_verdict.py — 7 pass; test_pr_linkcheck_renames.py and
    test_asm_policy.py still pass (24).
  • pyflakes tools/*.py in both directions: one undefined name on main
    (pr_linkcheck.py:342), zero across all of tools/ with this applied.
  • pr_linkcheck.py re-run end-to-end on TextureSequence::Prepare and MaterialChanger::Prepare are static #1390's head: completes, 310 files graded, no
    crash.
  • No src/ or config/ change, so the ROM build is untouched.

There is no Python lint step in CI today. Adding one would have caught this on the
commit that introduced it, and the baseline is already clean — but that is a separate
change, not folded in here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe

Since #1367 the PR validator has been unable to report on any PR that contains a
non-reproducing file. The draft-banner rule reads the module as `asm_policy` while
the import binds it as `AP`:

    import asm_policy as AP
    ...
    if w == "NO-REPRO" and asm_policy.has_draft_banner(text):

so the first NO-REPRO verdict raises NameError mid-loop. The worker then reports
"worker error: pr_linkcheck exited 1 without a report" with no verdict for any file,
including the ones it had already graded. PR #1390 is the first PR to hit it.

The rule is only reachable when a file FAILS, which is the one path a green CI run
never exercises -- so it was untestable where it sat, and untested. Both halves are
fixed here: the two source-text overrides move out of main() into `source_policy`,
and tools/test_pr_linkcheck_verdict.py drives them directly, including the collision
the old inline comment asserted could not happen (a bannered transcription grades
DRAFT, not RAW-ASM).

Behaviour is otherwise unchanged: the extracted function preserves the original
order, and a draft banner still does not excuse WRONG.

Confirmed with pyflakes in both directions -- one undefined name on main, zero across
all of tools/ with this applied. There is no lint step in CI today; adding one is a
separate change, but the baseline for it is clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
@tangos-validator

tangos-validator Bot commented Aug 11, 2026

Copy link
Copy Markdown

✅ PR validation — Passed

noverify: no source/build-data changes in this PR

Each changed src/*.c|*.cpp is compiled and its relocated bytes compared to the binary data on a private build box. Passing requires every changed file to reproduce the ROM byte-for-byte with correct relocation targets — this catches WRONG-DEST relocations and non-reproducing near-misses that ledger-scoped linkcheck skips.

@andrewboudreau
andrewboudreau merged commit 46db7b2 into main Aug 11, 2026
2 checks passed
@andrewboudreau
andrewboudreau deleted the fix/pr-linkcheck-draft-nameerror branch August 11, 2026 13:20
andrewboudreau added a commit that referenced this pull request Aug 11, 2026
…#1393)

The file has not compiled since it gained `#include "decl_common.h"`. That header
declares

    extern void func_ov002_020bdd2c(void*);
    extern void func_02073244(void*, int, int, void (*)(void*));

inside `extern "C"`, and the file then declared the same names again taking `char *`
and `(int, int, int, void (*)(void))`. Same C linkage, different parameter types, so
it is not a redeclaration but an attempt to overload a C-linkage name -- which
mwccarm rejects outright:

    src/_ZN6Player16CleanupResourcesEv.cpp:17: illegal function overloading
    ... :18, :19, :20, :23
    Errors caused tool to abort.

Nothing caught it, because the failure removes the file from every gate that could
have. A file that will not compile is not eligible, so it is not enrolled; its
delinks entry has no `complete`, so dsd hands the range to the original ROM bytes and
the build stays 106/106 without ever touching the source. It went unbuilt through
#919, #994, #1032 and #1382 -- the last of which edited it.

Fix is to declare only what decl_common.h does not. The three that remain are absent
from it; `_ZN7Vector3D1Ev` is spelled with the `this` it actually takes, which is
also what makes the `func_02073244` argument type-correct without a cast.

THE FUNCTION IS A BYTE MATCH. Once it compiles, all 0x730 bytes at 0x020e32d4 in
ov002 reproduce under the pin, with correct relocation destinations -- so this is not
a repair of a near-miss but the recovery of a match that has been sitting unreadable
in the tree. Enrolled here rather than left as rombytes.

VERIFIED

  build_pin      (True, '2004/b56') at 0x020e32d4, size 0x730, ov002
  pr_linkcheck   ok, 1 slot, correct destinations
  eligible.py    10,814 / 11,162 -- main is 10,813, and the diff is this symbol alone
  rombuild -j16  106/106 exact, 10,814 source-built, 0 mismatching, 87.88% -> 87.96%

Found while diagnosing why #1390's validation reported a worker error: with the
NameError in tools/pr_linkcheck.py fixed (#1392), the gate could finally grade this
file, and graded it NO-SYM. The verdict is correct and predates #1390 -- clean main
fails to compile it identically.


Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
andrewboudreau added a commit that referenced this pull request Aug 11, 2026
…#1395)

#1392 fixed a NameError in tools/pr_linkcheck.py that had blocked PR validation
outright: the module was bound as `AP` and read as `asm_policy`, on a branch only
reachable when a file graded NO-REPRO. The first PR to contain a declared draft got a
traceback instead of a verdict, and the worker discarded the results it had already
computed. This is the gate that stops it happening twice.

A gate's failure branch is the one path a green CI run never exercises. No ROM build
will ever catch a defect there -- these tools are not compiled into the ROM -- and
only static analysis reaches that code without running it. pyflakes finds this one in
under a second across all 186 tracked files.

NARROW ON PURPOSE, and this is the part worth arguing with. The gate fails on
unresolvable names and unparseable files, and on nothing else. The tree has ZERO of
those, so it lands green and every future red is a real regression.

It also has 32 style findings -- unused imports, unused locals, f-strings without
placeholders. Gating on those would have meant one of two bad outcomes: landing this
red, or a 32-file cleanup commit in the same PR that buries the actual signal. A gate
that lands red gets switched off, and then it catches nothing at all. So the style
findings are counted on every run, printed with --advisories, and not gated.
Ratcheting them downward the way langmode-ratchet.yml does is a fair follow-up; it
needs a banked baseline, and this deliberately does not.

pyflakes is pinned to 3.0.1. A floating version can add a check, and this job turning
red on a PR that touched no Python would be indistinguishable from a real regression
-- which is exactly the confusion a stale ratchet caused earlier this week.

VERIFIED

  the #1367 defect, reconstructed verbatim, is caught:
      repro.py:5: undefined name 'asm_policy'   (exit 1)
  an unparseable file is caught, separately from the name check
  184 -> 186 tracked files, 0 unresolvable, 0 unparseable, 32 advisory
  7 tests pass under `python -m unittest tools.test_check_python_names`, which is how
  the workflow runs them -- stdlib only, so the runner needs no dependency beyond the
  pinned pyflakes

The gate caught its own author on the way in: the first draft imported
pyflakes.reporter without using it, and the advisory count went 32 -> 33.


Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant