Skip to content

ci: let clang-tidy-diff parse the two AUTOMOC self-including sources, and name option-coverage by its job id (fixes #624, fixes #637) - #647

Merged
Yaraslaut merged 2 commits into
masterfrom
laneCIA-batch-637-624
Sep 21, 2026
Merged

Yaraslaut merged 2 commits into
masterfrom
laneCIA-batch-637-624

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

Two tickets on the ci.yml / clang-tidy tree, one commit each.


#637ci.yml named a job that does not exist

linux-all-features' MORPH_BUILD_BANK_GUI rationale pointed the reader at "the check-workflow-option-coverage job below". The job is option-coverage; check_workflow_option_coverage.py is the script it runs. Reproduced on c55ea5b7:

$ grep -n 'check-workflow-option-coverage' .github/workflows/ci.yml
1838:          # fetch requires. The check-workflow-option-coverage job below is
$ grep -rn '^  check-workflow-option-coverage:' .github/workflows/
$ grep -n '^  option-coverage:' .github/workflows/ci.yml
2449:  option-coverage:

The replacement names both, so the next reader does not have to guess which of the two the sentence meant.

Swept for the same defect tree-wide. Over every # comment line in .github/workflows/*.yml, the hyphenated lowercase tokens immediately preceding the word "job" are automoc-include-lint, scenario-coverage, ladder-tests, linux-all-features, linux-coverage, option-coverage and dependency-free. All but the last resolve to a real job id somewhere in the tree; dependency-free job is an adjective. This was the only stale one.

The residual, decided explicitly: accepted, and filed

#638's check_workflow_job_banners.py would not have caught #637, and still would not. Its rule A inspects the line after a banner and rule B inspects whether a job has one; a job id inside a comment body is neither. So nothing gates this class.

I am accepting that residual here rather than closing it, for two reasons:

  1. It is a separate change, not a line of this one. This repository requires a gate to carry a self-test (test_check_workflow_option_coverage.sh, test_check_workflow_job_banners.sh), so closing it means a new script, a new self-test, and a new CI step. AGENTS.md is explicit that a finding gets filed and linked rather than folded in.
  2. The cheap version of the rule is not free of judgement. The measurement above is also the feasibility study: restricted to hyphenated lowercase tokens, the rule flags two lines tree-wide — ci.yml's all-features Configure step points at a job name that does not exist #637's, and one false positive — and it cannot catch a stale reference to a single-word job id such as valgrind or windows at all.

Filed as #645, carrying that measurement.


#624clang-tidy-diff could not parse two translation units

Both sources end with #include "<own-basename>.moc". AUTOMOC writes that header at build time; the job configures and never builds. Reproduced locally on c55ea5b7 with the job's own configure flags and clang-tidy 22.1.8, one line added to each file:

/.../examples/common/testkit/test_qml_surface.cpp:757:10: error: 'test_qml_surface.moc' file not found [clang-diagnostic-error]
/.../src/qt/forms/tests/tst_main.cpp:70:10: error: 'tst_main.moc' file not found [clang-diagnostic-error]
exit=1

Which of the two fixes, and what the other would have cost

The ticket offered "restore AUTOMOC generation" against "filter clang-diagnostic-error for these TUs" and said to pick one. The second one does not exist. clang-tidy does not let a compiler error be filtered — measured on tst_main.cpp with the moc absent:

--- baseline (no filter)              : exit=1   'tst_main.moc' file not found [clang-diagnostic-error]
--- -checks=-clang-diagnostic-error   : exit=1   'tst_main.moc' file not found [clang-diagnostic-error]
--- -checks=-clang-diagnostic-*       : exit=1   'tst_main.moc' file not found [clang-diagnostic-error]
--- -warnings-as-errors= empty        : exit=1   'tst_main.moc' file not found [clang-diagnostic-error]

So "filter it" would have had to mean grepping clang-tidy-diff.py's output and overriding its exit code — morph#479's defect, a gate that cannot fail, rebuilt on purpose. That is what the rejected option costs, and it is why this is not a judgement call between two tenable designs.

What generating them costs, measured

The comment at the old :2229 argues against a build on caching grounds, so the cost was measured rather than assumed. On a 12-core Linux box with USE_COMPILER_CACHE=OFF (the local default is fastcache-cc, which reported the same work as 3s and would have been a useless number):

edges compilations links moc runs wall
every *_autogen target (91) 499 264 91 179s
the two needed 100 67 7 9 31s

39 of the 67 are the vendored Lightweight ORM, pulled in because a <target>_autogen target depends on its target's link dependencies — that, not moc, is what the step costs. So the step names its two targets instead of building them all. Compiler caching stays unwired: 67 objects against the 703 a real build of this configure would compile.

A four-core hosted runner will be slower than 31s. That is inferred, not measured.

Both comments updated, as the ticket required

  • The "No Build step" paragraph answered "which generated headers exist without a build?" for configure_file() output while reading as though it had settled the question. It now says which kind it covers and points at the AUTOMOC step for the kind it does not, and reads "no full Build step".
  • The -Wno-missing-include-dirs comment made the matching over-broad claim — that no source under examples/, include/ or src/ includes a generated header. What the "Check no generated moc include ascends" guard covers is an ascending moc_<name>.h; a same-name .moc in the target's own autogen dir is a different shape and the flag says nothing about it, because the error is the file being absent, not the directory. Corrected.

The list is written out; the check is derived

Two target names are spelled out in the step, so they are greppable and reviewable. The check underneath rescans every tracked C++ source for the self-include idiom and fails if a .moc a source names is still missing — which is what a third source adopting the idiom looks like — and fails if it finds no self-include at all.


Verification

ci.yml is the only file this branch touches. The safety of #624 rests on one claim: the clang-tidy job now analyses both translation units to completion and reaches the changed line. A green clang-tidy-diff would not show that, so it was made to go red on a line in each file. int laneProbeReachMarker() { return 0; } inserted above each .moc include, run with the job's own clang-tidy-diff.py arguments:

--- violation: clang-tidy-diff exit=1
/.../src/qt/forms/tests/tst_main.cpp:69:5: error: function 'laneProbeReachMarker' can be made static
  or moved into an anonymous namespace to enforce internal linkage [misc-use-internal-linkage,...]
/.../examples/common/testkit/test_qml_surface.cpp:756:5: error: function 'laneProbeReachMarker' can be
  made static or moved into an anonymous namespace to enforce internal linkage [misc-use-internal-linkage,...]
    clang-diagnostic-error count: 0

--- harmless: clang-tidy-diff exit=0
    clang-diagnostic-error count: 0

A finding reported at tst_main.cpp:69 and test_qml_surface.cpp:756 — the changed lines — is the proof; the same diff before this change produced two clang-diagnostic-errors and no findings at all. Both probe edits were reverted; neither source file is modified by this branch (test_qml_surface.cpp is held by a concurrent lane and was only read and probed).

The step's own controls, from a clean configure-only tree (rm -rf build/clang-debug, configure, 0 .moc on disk), running the step body extracted from ci.yml rather than a retyped copy:

=== 2. NEGATIVE CONTROL: the step's own check, run before the build ===
::error::examples/common/testkit/test_qml_surface.cpp: test_qml_surface.moc was not generated -- ...
::error::src/qt/forms/tests/tst_main.cpp: tst_main.moc was not generated -- ...
check-before-build exit=1  (must be 1)

=== 3. VACUITY CONTROL: same check with the scan finding nothing ===
::error::no tracked source self-includes a .moc -- this step's scan has stopped detecting the idiom it exists for
empty-scan exit=1  (must be 1)

=== 4. the step as written ===
ok: examples/common/testkit/test_qml_surface.cpp -> test_qml_surface.moc
ok: src/qt/forms/tests/tst_main.cpp -> tst_main.moc
ok: 2 self-included .moc header(s), 0 missing
step exit=0

Gates run locally after the edits, all green:

python3 scripts/check_workflow_job_banners.py .     -> all 24 section banner(s) introduce the job they describe
bash scripts/test_check_workflow_job_banners.sh     -> all cases passed.
python3 scripts/check_workflow_option_coverage.py . -> 16 MORPH_BUILD_* option(s) declared, all accounted for
bash scripts/check_tidy_suppression_scope.sh clang-tidy . -> tests/.clang-tidy records the reach it actually has
bash scripts/check_spec_citations.sh                -> Prose lint OK
bash scripts/check_ci_clang_pin.sh                  -> all 7 CI-clang-pin assertion(s) agree with ci.yml

.github/workflows/ci.yml also parses as YAML after the edit — worth stating because the first draft of the new step was named Generate the AUTOMOC headers two sources #include directly, and YAML silently truncated the name at the unquoted #. Caught by parsing the file and printing the step names back.

Not verified

  • Nothing was run on a hosted runner. Local Qt is the distro's 6.11.2, not aqtinstall's QT_VERSION; local clang-tidy is 22.1.8, not apt.llvm.org's clang-tidy-22; local clang++ stands in for clang++-22.
  • The CI wall-clock cost of the new step. 31s on 12 cores is measured; four cores will be worse.
  • Whether the step behaves when a future source self-includes a .moc from a target this configure does not enable — that path prints a ::warning:: and continues, by design, since clang-tidy cannot analyse such a file either.

Findings filed, not folded

🤖 Generated with Claude Code

https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW

Yaraslaut and others added 2 commits September 21, 2026 00:46
…637)

linux-all-features' MORPH_BUILD_BANK_GUI rationale referred the reader to
"the check-workflow-option-coverage job below". No job by that name exists
in .github/workflows/: the job is `option-coverage`, and
check_workflow_option_coverage.py is the script it runs. A reader who
searches ci.yml for the name the comment gives finds nothing, and the
sentence's whole purpose is to point at the standing guard.

Measured on c55ea5b, before the change:

    $ grep -n 'check-workflow-option-coverage' .github/workflows/ci.yml
    1838:          # fetch requires. The check-workflow-option-coverage job below is
    $ grep -rn '^  check-workflow-option-coverage:' .github/workflows/
    $ grep -n '^  option-coverage:' .github/workflows/ci.yml
    2449:  option-coverage:

The replacement names both -- the job id a reader can jump to, and the
script it runs -- so the next reader does not have to guess which of the
two the sentence meant.

Swept the rest of the tree for the same defect: over every `# ...` comment
line in .github/workflows/*.yml, the hyphenated lowercase tokens
immediately preceding the word "job" are automoc-include-lint,
scenario-coverage, ladder-tests, linux-all-features, linux-coverage,
option-coverage and dependency-free. All but the last resolve to a real job
id in some workflow; "dependency-free job" is an adjective, not a
reference. This was the only stale one.

Nothing gates such a reference -- #638's check_workflow_job_banners.py
pairs banners to jobs, and a job id inside a comment body is not a banner.
That residual is accepted here and filed separately rather than folded in:
closing it needs a new script plus the self-test this repository requires
of a gate, which is its own change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW
fixes #624)

examples/common/testkit/test_qml_surface.cpp and src/qt/forms/tests/
tst_main.cpp both end with `#include "<own-basename>.moc"` -- the AUTOMOC
idiom for a Q_OBJECT declared in a .cpp. AUTOMOC writes that header at
build time, the clang-tidy job configures and never builds, so every PR
that touches either file failed the job on a parse error before a single
changed line was analysed.

Reproduced locally on c55ea5b, this job's configure flags, clang-tidy
22.1.8, one line added to each of the two files:

    /.../examples/common/testkit/test_qml_surface.cpp:757:10: error:
      'test_qml_surface.moc' file not found [clang-diagnostic-error]
    /.../src/qt/forms/tests/tst_main.cpp:70:10: error:
      'tst_main.moc' file not found [clang-diagnostic-error]
    exit=1

## Why generating, and not suppressing

The issue offered two fixes. Suppressing the diagnostic turns out not to
be one of them: clang-tidy does not let a compiler error be filtered.
Measured on tst_main.cpp with the moc absent, all three still exit 1 on
the same line --

    --- baseline (no filter)              : exit=1  'tst_main.moc' file not found
    --- -checks=-clang-diagnostic-error   : exit=1  'tst_main.moc' file not found
    --- -checks=-clang-diagnostic-*       : exit=1  'tst_main.moc' file not found
    --- -warnings-as-errors= empty        : exit=1  'tst_main.moc' file not found

so "filter it" would have meant grepping clang-tidy-diff.py's output and
overriding its exit code -- morph#479's defect, a gate that cannot fail,
rebuilt on purpose. That is what the rejected option would have cost.

Generating them costs a partial build, measured locally with
USE_COMPILER_CACHE=OFF on 12 cores: 100 ninja edges, 67 compilations, 7
links, 9 moc runs, 31s. 39 of the 67 are the vendored Lightweight ORM,
pulled in because a <target>_autogen target depends on its target's link
dependencies. Building *every* autogen target instead was measured at 264
objects and 179s, so the step names the two targets it needs. Compiler
caching is still not wired into this job: 67 objects against the 703 a
real build of this configure would compile.

## The comment the issue was really about

ci.yml's "No Build step" paragraph already reasoned about which generated
files exist without a build, answered it for configure_file() output, and
read as though it had settled the question. It now says which kind it
covers, and points at the AUTOMOC step for the kind it does not. The
-Wno-missing-include-dirs comment made the matching over-broad claim --
that no source includes a generated header -- when what its guard covers
is an ascending `moc_<name>.h`, not a same-name `.moc`; corrected too.

## Verification

Measured locally on this commit's tree, from a clean configure-only build
directory (`rm -rf build/clang-debug`, configure, 0 .moc files on disk),
running the step body extracted from ci.yml rather than a retyped copy:

    === 2. NEGATIVE CONTROL: the step's own check, run before the build ===
    ::error::examples/common/testkit/test_qml_surface.cpp: test_qml_surface.moc was not generated -- ...
    ::error::src/qt/forms/tests/tst_main.cpp: tst_main.moc was not generated -- ...
    ok: 2 self-included .moc header(s), 2 missing
    check-before-build exit=1  (must be 1)

    === 3. VACUITY CONTROL: same check with the scan finding nothing ===
    ::error::no tracked source self-includes a .moc -- this step's scan has stopped detecting the idiom it exists for
    empty-scan exit=1  (must be 1)

    === 4. the step as written ===
    ok: examples/common/testkit/test_qml_surface.cpp -> test_qml_surface.moc
    ok: src/qt/forms/tests/tst_main.cpp -> tst_main.moc
    ok: 2 self-included .moc header(s), 0 missing
    step exit=0

A green clang-tidy-diff would prove nothing on its own, so the job was
made to report a finding on the changed line in each of the two files. A
deliberate `int laneProbeReachMarker() { return 0; }` inserted above each
`.moc` include, with the job's own clang-tidy-diff arguments:

    --- violation: clang-tidy-diff exit=1
    /.../src/qt/forms/tests/tst_main.cpp:69:5: error: function 'laneProbeReachMarker'
      can be made static or moved into an anonymous namespace [misc-use-internal-linkage,...]
    /.../examples/common/testkit/test_qml_surface.cpp:756:5: error: function 'laneProbeReachMarker'
      can be made static or moved into an anonymous namespace [misc-use-internal-linkage,...]
        clang-diagnostic-error count: 0

    --- harmless: clang-tidy-diff exit=0
        clang-diagnostic-error count: 0

Both translation units are now analysed to completion and the changed
line is reached; the same diff before this change produced two
clang-diagnostic-errors and no findings at all.

Not verified: any of this on a GitHub hosted runner, or against
aqtinstall's Qt ${QT_VERSION} rather than the local distro Qt 6.11.2 and
apt.llvm.org's clang-tidy-22 rather than local 22.1.8. The CI timings will
be worse than 31s on four cores.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW
@Yaraslaut

Copy link
Copy Markdown
Member Author

Runner verification

The ticket's framing was wrong and the lane was right to say so. #624 presented a trade-off between generating the AUTOMOC headers and filtering clang-diagnostic-error. I reproduced the lane's finding independently, with clang-tidy 22.1.8 on a two-line probe that self-includes a nonexistent .moc:

control: clean file, real check enabled                       exit=0
missing .moc, -checks=…,-clang-diagnostic-error               exit=1  file not found
missing .moc, -checks=…,-clang-diagnostic-*                   exit=1  file not found

The control matters: my first attempt at this probe passed -checks=-clang-diagnostic-* alone, which disables every check and exits 1 with "no checks enabled" — both the broken file and a clean one exited 1, so the measurement said nothing. Re-run with a real check enabled, the clean file exits 0 and both filtered forms still exit 1. So the diagnostic genuinely cannot be suppressed, and presenting two designs would have been presenting one real option and one that does not exist.

The step's two vacuity guards are in the script, not just in the report — read from the diff:

if found == 0:
    print("::error::no tracked source self-includes a .moc -- this "
          "step's scan has stopped detecting the idiom it exists for")
    sys.exit(1)
print(f"ok: {found} self-included .moc header(s), {missing} missing")
sys.exit(1 if missing else 0)

A scan that stops matching the idiom fails instead of reporting green, and the list is derived from git ls-files rather than hardcoding the two known files, so a third self-including source is covered the day it is added. That is the right shape for this class of gate.

What I did not verify, and it is the branch's load-bearing claim. That the clang-tidy job now analyses both TUs to completion and reaches the changed line rests on the lane's local probe (laneProbeReachMarker findings at tst_main.cpp:69 and test_qml_surface.cpp:756, clang-diagnostic-error count: 0). Reproducing it needs a full Qt configure, which I did not run. I am accepting it on the strength of the probe's design — a marker on the changed line, findings at that line, and the same diff before the change producing two clang-diagnostic-errors and no findings — not on a green CI leg.

And the lane is right that CI cannot confirm it here. This PR's diff is ci.yml only, so clang-tidy-diff will analyse no C++ at all and go green without exercising the effect. The new step still runs and fails loudly if the mocs are absent. When this lands, the first PR that touches either file is the real test.

#646 is the consequence worth flagging before merge: 84 findings become reachable (81 in test_qml_surface.cpp, 73 of them misc-const-correctness). Nothing breaks on landing, but the first PR touching one of those lines inherits debt that is not its own. That is filed, not folded, and it is the kind of thing I would rather have on a ticket than discovered by whoever next edits that file.

#645 (nothing gates a prose job-id reference in a comment) records the #637 residual with the measurement showing why the cheap rule is not clean — two hits tree-wide, one of them a false positive, and no coverage of single-word job ids. Accepting with that recorded beats closing it by assertion.

Not merged: CI incomplete. Next sweep counts the checks.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW

@codecov

codecov Bot commented Sep 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Yaraslaut
Yaraslaut merged commit f7c231d into master Sep 21, 2026
50 checks passed
Yaraslaut added a commit that referenced this pull request Sep 21, 2026
…uding TUs, and drop a deleted workflow from a docstring (fixes #646, fixes #643) (#653)

* testkit/qt: clear the 84 clang-tidy findings the two AUTOMOC self-including TUs now report (fixes #646)

#647 made the clang-tidy job build `ladder_common_tests_autogen` and
`morph_forms_qml_tests_autogen`, so `examples/common/testkit/test_qml_surface.cpp`
and `src/qt/forms/tests/tst_main.cpp` parse for the first time and are
analysed. Nothing was broken -- clang-tidy-diff reports only changed lines --
but the first PR to touch one of those lines would have inherited findings
that were not its own.

Re-measured on 0067b5b with the clang-tidy job's own configure flags and
clang-tidy 22.1.8 (CI's pinned major), both AUTOMOC targets built first:
81 findings in test_qml_surface.cpp (73 misc-const-correctness, 3
readability-convert-member-functions-to-static, 2
readability-inconsistent-declaration-parameter-name, 2
readability-identifier-length, 1 bugprone-easily-swappable-parameters) and 3
in tst_main.cpp. That reproduces the #647 lane's figure at c55ea5b exactly.

77 of the 84 are fixed rather than suppressed:

  * 73 `misc-const-correctness` -- local `QTemporaryDir` and fixture-bridge
    declarations that are never mutated. Applied with clang-tidy --fix, then
    rewritten to the west-const spelling the rest of the file uses.
  * 2 `readability-identifier-length` -- `id` -> `rowId`, `ok` -> `okay`.
    Safe: QmlSurfaceAudit reads `QMetaMethod::name()` and `parameterCount()`
    and never a parameter name, and the QML fixture text is unchanged.
  * 1 `misc-use-internal-linkage` -- `MorphFormsQmlTestSetup` moves into an
    anonymous namespace; QUICK_TEST_MAIN_WITH_SETUP expands in the same TU.
  * 1 `readability-redundant-access-specifiers` -- the explicitly defaulted
    default constructor and its `public:` are removed, which also removes the
    redundancy, since Q_OBJECT ends in `private:`.

The remaining 7 get individually reasoned NOLINTNEXTLINEs, reason above the
directive (#631/#627's rule) -- no NOLINT sweep and no new `.clang-tidy`
entry, which is what #632 was about:

  * 4 `readability-convert-member-functions-to-static` on Q_PROPERTY readers,
    a Q_INVOKABLE and a Qt Quick Test setup slot. The reason is shape, not
    legality: the static form was measured to compile and moc registers the
    same property, but no bridge these fixtures stand in for has a static
    property reader, and a Qt slot is a member function by definition.
  * 2 `readability-inconsistent-declaration-parameter-name` on the two
    signals. moc's generated definitions name the parameters `_t1`/`_t2`, so
    no edit to the declarations can remove the mismatch; the finding reaches
    these files only because the classes are declared in a .cpp.
  * 1 `bugprone-easily-swappable-parameters` on the file-local `writeQml`
    helper's two adjacent `const QString&`.

Verified, not asserted:

  * Both files are in this configure's compile_commands.json (703 entries,
    695 in-workspace, 270 under examples/ -- above #649's 600/200 floors).
  * clang-tidy exits 0 on both files afterwards, and with every
    NOLINTNEXTLINE line stripped it exits 1 reporting exactly the 6 + 1
    suppressed findings again. Each directive is load-bearing and the TUs are
    really analysed, rather than clean because nothing looked at them.
  * `ladder_common_tests "[qml-surface]"`: 182 assertions in 36 test cases,
    all passing.
  * `morph_forms_qml_tests -input src/qt/forms/tests`: 292 passed, 0 failed,
    the two corpus-reading suites among them -- which is what proves the
    setup slot still runs after the anonymous-namespace move.
  * clang-format 22.1.8 clean; check_nolint_directives.sh, check_bidi_controls.py,
    check_tidy_suppression_scope.sh, check_automoc_includes.sh,
    check_catch_test_names.sh all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW

* ci: drop the deleted suppression-guard.yml from the banner gate's docstring (fixes #643)

#635 deleted .github/workflows/suppression-guard.yml; the checker's "## Scope"
paragraph still named it as one of the single-job workflows the gate skips. It
was the last reference to that file in the tree:

    $ git grep -n "suppression-guard" -- .
    scripts/check_workflow_job_banners.py:50:suppression-guard.yml and the two wasm workflows are single-job files that have

Gate behaviour was never affected and is not affected now -- the skip is
derived per file, not read from that list. Measured on 0067b5b, over the five
workflows with zero banners:

    ok: .github/workflows/docs.yml: no section banners, not in the banner style
    ok: .github/workflows/mutation.yml: no section banners, not in the banner style
    ok: .github/workflows/spec-sync.yml: no section banners, not in the banner style
    ok: .github/workflows/wasm-demo.yml: no section banners, not in the banner style
    ok: .github/workflows/wasm-ladder.yml: no section banners, not in the banner style

    ok: all 24 section banner(s) introduce the job they describe

So the name is deleted rather than swapped for another: with it gone the
sentence enumerates exactly the five files a run reports as skipped, and there
is no sixth current example to put in its place.

A second paragraph says so explicitly -- the list is an illustration with a
shelf life, nothing reads it, and the run's own output is the current list --
so the next workflow deletion dates one sentence instead of producing a third
round of this.

Verified: `python3 scripts/check_workflow_job_banners.py .` and
`bash scripts/test_check_workflow_job_banners.sh` both pass; the five skipped
files and their single-job counts were enumerated from the tree with the
checker's own BANNER_RE/JOB_KEY_RE rather than read off the docstring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW

---------

Co-authored-by: Claude Opus 5 (1M context) <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