Let PR validation grade a failing file instead of dying on it - #1392
Merged
Conversation
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
✅ PR validation — Passednoverify: no source/build-data changes in this PR Each changed |
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
andshort-circuits, so the line is only evaluated once a file gradesNO-REPRO—and then it raises
NameErrorin the middle of the classification loop. The workerreports
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:
The crash file is
src/_ZN3MrI13InitResourcesEv.cpp, which sorts immediately aftersrc/_ZN3Amp8BehaviorEv.cpp— the last line the validator printed before dying. Itcarries a
// NONMATCHINGbanner, so the correct verdict is DRAFT, a pass. Therule was written for exactly this file and had never once run.
The
NO-SYMis real and pre-existing, not from #1390:src/_ZN6Player16CleanupResourcesEv.cppdoes not compile on cleanmaineither(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 intosource_policy(worst, text)andtools/test_pr_linkcheck_verdict.pydrives themdirectly, 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.pyandtest_asm_policy.pystill pass (24).pyflakes tools/*.pyin both directions: one undefined name onmain(
pr_linkcheck.py:342), zero across all oftools/with this applied.pr_linkcheck.pyre-run end-to-end on TextureSequence::Prepare and MaterialChanger::Prepare are static #1390's head: completes, 310 files graded, nocrash.
src/orconfig/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