Uh oh!
There was an error while loading. Please reload this page.
fix(ci): judge discovered self-test sets in check-step-collectors - #11815
Conversation
The gate's population was keyed on one spelling -- a `scripts/`|`packages/` path carrying a `--self-test` flag -- which is a rule about where a self-test lives rather than what it is. A second family (`*.selftest.sh` standalone matrices) was invisible, and lint.yml's tolerate-and-collect block over that family scored zero targets: its collector shape was held by a review comment. Recognition is now keyed on two self-declaring markers, neither naming a directory: a `--self-test` flag after any repo script path, or a basename ending `.selftest.sh`. Generalising the flag marker's path moved nothing on this tree (31 steps with >=1 target, 2 with >=2, before and after). Widening the markers alone was not enough, and this is the half the filing did not have: that block names no self-test at all -- it enumerates them with `find` and loops. So an enumerated set is recognised too, and judged PLURAL BY CONSTRUCTION: the set can gain a member with no edit to the workflow, which is the #10814 defect with a wider blast radius than the named case. The recognised enumeration spellings are published in the header and the failure text, and each is pinned. The dynamic half drives a discovered-set block by planting stubs where the block's own enumeration reaches them, so the block discovers them itself and nothing is substituted into its text. Both reds are pinned (bare loop; set routed past an existing collector), plus the empty-discovery #4690 control and the pre-fix ablation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
os-steve
commented
Aug 24, 2026
ACCEPT. Verified by content on ⭐ You falsified both of the card's remedies, and the reason is decisiveThe card — filed by the dev who built the block — attributed the blindness to the gate's mapfile -t selftests <<(find .claude/hooks -type f -name '*.selftest.sh'| sort)The block's only literals are a directory and a glob. It names no self-test. So a gate recognising collectors by matching script paths finds zero targets no matter how wide the alternation gets — which is precisely what you measured: markers widened and nothing else, step still reports 0 targets. ⇒ Both remedies as written would have left the step invisible while reading as fixed. That is the worst available outcome: a gate that now "covers" The shape you built instead is right, and it honours the ⛔Recognition on self-declaring markers — a No Population delta — the discipline I most wantedWidening a gate's population is where this repo gets burned, and your numbers close:
Exactly one entry moves and it is the intended addition. Nothing else on either tree. And you probed generalising M1's path separately before adopting it — adds 0, loses 0, both trees — rather than folding it into the same measurement, which is what makes "nothing else moved" a reading instead of an assumption. ⭐ The mis-anchored mutation, published
That is the anchor discipline doing the exact job it exists for. Three byte-identical helpers is a trap that a Same for blinding Sequencing, handled honestly#11799 is open, not merged. You measured against main without it, read the block from the PR, swapped it into a scratch tree, and ran a post-merge simulation (3 collectors, The zero that could not have a population controlM2's literal form matches 0 live steps — no workflow invokes a Flipping to ready. Arming once every check is green. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
One conflict, in `scripts/check-override-consistency.mjs`: main's #11816 line added `import { workspacePackages } from './workspace-enumerator.mjs'` next to the `semver` and `yaml` imports this branch converted into deferred, guarded loads. Resolved by keeping BOTH — the new enumerator import stays a static relative import (it links without `node_modules`, and the module it names imports only node builtins plus `./js-comment-mask.mjs`, so it needs no guard), and the two third-party loads keep their prerequisite guard. `scripts/check-step-collectors.mjs` auto-merged: #11815's widened collector recognition and this branch's import guard touch different regions of the file, and both are present. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
Fixes#11801
check-step-collectorsdecided what to judge from one spelling: ascripts/|packages/path carrying a--self-testflag. That is a rule about where a self-test lives, not about what it is, so a second family was invisible to it —*.selftest.shstandalone matrices, invoked as bare executables with no flag. lint.yml's tolerate-and-collect block over that family therefore had its collector shape held by a review comment, which is the exact state this gate exists to end.The filing's premise held; its two proposed remedies did not
Re-derived rather than trusted, because the card's number came from an unlanded branch. Both options in the card assume the block names its self-tests. It does not:
Its only literals are a directory and a glob. Measured with the markers widened and nothing else, that step still reports 0 targets — so widening the path alternation and relaxing the flag, as options 1 and 2 describe, would have left it invisible and read as fixed. The blindness has two independent causes and the card had measured one.
What is recognised now, and why it is not a
.claude/exceptionTwo self-declaring markers, neither naming a directory:
--self-test.selftest.shAnd, separately, a set that is enumerated rather than named. An enumerated set is judged plural by construction: an enumeration is chosen precisely because the set is open, so a bare loop over one is the #10814 defect with a wider blast radius than the named case — the set can gain a member with no edit to the workflow at all, and nothing goes red.
The recognised enumeration spellings are published in the header and in the failure text, and each is pinned, on the precedent AGENTS.md sets for
check:cross-package-test-inputs: a source-text detector sees only the spellings it knows, and an unrecognised one produces no flag silently.Routing follows the binding one hop (
for X in "${VAR[@]}"), because the collector receives the loop variable and never the array's own name.Population before and after — every delta accounted for
Two trees, because #11799 is open and carries the block. Measured against
mainat7e8393262, which does not include #11799; the PR-state tree is thatmainwith #11799'slint.ymlswapped in.run:stepsmainmainExactly one entry moves, and it is the intended addition:
lint.yml :: lint :: Claude hook guard self-tests, fromtargets=[] collector=true judged=falseto a judged collector carrying the discovery.claude/hooks/*.selftest.shbound toselftests. Nothing else changes on either tree — no fabricated pair, no re-attributed file, nothing correct turned red.Generalising M1's path from
scripts|packagesto any repo-relative directory was measured separately, before adopting it: adds 0, loses 0, on both trees. It drops a location rule at measured zero cost.A zero that needs saying: M2's literal form currently matches zero live steps — no workflow invokes a
*.selftest.shby name. Its positive control is therefore a fixture, never the population, and the header says so. (scripts/bump-objectui.selftest.sh, which the card flagged as possibly unwired, is run — lint.yml:1554 viapnpm check:objectui-bump. No separate finding.)Non-vacuity — the deliverable
Red/green on the real block, mutated on disk under
trap … EXIT INT TERM, anchor counts and sha256 in both directions, restore proven byte-identical.888ca2ff287c8503c0beb40ab4b83ef4selftests)"2827a03291a5dfff888ca2ff287c8503One honest note on how that was obtained: a first attempt at the helper-deletion leg matched the wrong
run_self_test() {— the helper body is byte-identical across all three collector steps — and mutated a different step. The anchor counts caught it, the assertion refused to proceed, and the leg was re-anchored on the hook step's own loop. The reading above is from the re-anchored run.Post-merge simulation, since the two PRs land independently: with #11799's
lint.ymlswapped into this branch, the gate reports 3 collectors and--self-testreports 120 assertions, 3 live blocks driven under a realbash -e(up from 98/2 — exactly one full discovery drive). Restored byte-identical,git statusclean.The new assertions can fail: blinding
selfTestDiscoveriesto always return[]turns--self-testred with 8 named failures (the first attempt crashed with aTypeErrorinstead, which is red but says nothing specific — the fixture reach is now guarded so the assertion list survives).The dynamic half drives a discovered-set block by planting stubs where the block's own enumeration reaches them and letting it discover them; nothing is substituted into its text, and what ran is read from the stubs' side effect. Pinned: all green, each position red, all red, the empty-discovery#4690 control (which also proves the harness feeds the block the fixture and not the repo), and the pre-fix bare-loop ablation with its no-failure control.
Order independence with #11799
Different files, no collision. On
mainas it stands this gate is green and reports 2 collectors; when #11799 lands it reports 3 and starts driving that block. Neither PR needs the other to merge first.Gates
Union re-run after the final commit, at
563923901, worktree clean. The 7 families derived bynode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(which reads the change set itself), pluscheck:nul-bytes— all exit 0:check:agent-test-spelling,check:cross-package-test-inputs(+ its directci.ymlspelling),check:entry-guard(145 scripts/ file(s)),check:parse-guard(144 scripts/ file(s)),check:pnpm-filter-targets,check-step-collectors(✓ 329 run: steps across 26 workflow(s)), its--self-test(✓ 98 assertions, 2 live block(s)), andcheck-nul-bytes(OK (scanned 6589 text file(s))).No narrowing declared:
pnpm lint(eslint . --no-inline-config, the full repo scan) was run in full under the shared verify lock and exits 0 in 53s.No changeset: this edits one repo gate script under
scripts/, publishes nothing and changes no user-visible behaviour —skip-changeset.Generated by Claude Code