test(scripts): check-shell-escape-residue reds on an agent-tree document no scan root reaches (#7413) - #8010
Merged
baozhoutao merged 1 commit intoSep 6, 2026
Conversation
…ent no scan root reaches `SCAN_ROOTS` answers "what is scanned". Nothing answered "is anything in the agent tree NOT scanned", and a `minFiles` floor cannot: it detects a root that COLLAPSED, never a document that was never on the surface. objectui#7403 lost 18 fenced blocks to a move that no floor could see; the same shape one step out is a new `.claude/agents/reviewer.md`, where `.claude/skills` still returns its 4 files, every floor is satisfied, every root resolves, and the new document is simply unjudged. Adds `COVERAGE_TREES`: trees in which every `.md`/`.mdx` must be reached by some declared root. Coverage is a set-membership test over the walk's OWN output -- `scan` collects every path `listDocuments` returned, then asks the same `listDocuments` for the tree -- so it cannot disagree with the scan about what a document is. A second glob would be a second answer to one fact. Two trees. `.claude` is the card's case: the declared row is a proper subtree, so the tree can grow documents outside it (4/4 covered today). `skills` is the generalisation, measured: its row is the whole tree, so the test is a tautology today (16/16) and costs one extra walk of 16 files -- declared anyway because it stops being a tautology under the one edit nothing else catches, NARROWING or DELETING the row. A vanished root is loud; a deleted row leaves nothing behind to be loud about. `content/docs` is deliberately not a coverage tree: same mechanism, but a completeness claim over published prose that no card has ruled. An unresolvable coverage tree is loud for the reason a scan root is, and the coverage figure rides in the verdict line so a green shows its population. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MM7kaS4dPpYHV5BsMyu4tQ
This was referenced Sep 6, 2026
Contributor
Author
|
Standing down on Generated by Claude Code |
baozhoutao
marked this pull request as ready for review
September 6, 2026 09:20
baozhoutao
deleted the
claude/issue-7413-shell-escape-residue-claude-root
branch
September 6, 2026 09:37
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.
Fixes #7413
Direction 2 of the card, which is the general form: the gate now judges whether anything in the agent tree is off its scan surface, as its own verdict — not a widened row.
The assertion's design
SCAN_ROOTSanswers what is scanned. Nothing answered is anything in the agent tree NOT scanned, and nominFilesfloor can: a floor detects a root that collapsed, never a document that was never on the surface. Three shapes, only one of which had a signal before this:minFilesfires (#7403's fix)unresolvedfiresSCAN_ROOTSrow is deletedCOVERAGE_TREESadds the missing judgement: trees in which every.md/.mdxmust be reached by some declared row, reported by file name when one is not.It cannot disagree with the scan about what a document is. Coverage is set membership in the walk's own output —
scancollects every pathlistDocumentsreturned for every resolved root, then asks the samelistDocumentsfor the tree. One walk, oneDOC_EXTENSIONS, one definition. A second glob or a prefix match on the row spec would be a second answer to one fact (#3261/#3279), and the drift would land on the side that reads as coverage.An unresolvable coverage tree is loud for the reason a scan root is — a mistyped
.claudecovers nothing and reads as coverage forever — and it carries aroleso the remedy names the right declaration list. The coverage figure rides in the verdict line, so a green still shows its population:The
skills/-side measurement the card asked forIt generalises, at the cost of one extra walk of 16 files, so it is included.
.claude— the card's case. The declared row is a proper subtree, so the tree can grow documents outside it. 4/4 covered one1545cf; green today by construction, and its whole job is the next file.skills— the row is the whole tree, so today the test is a tautology (16/16). Declared anyway because it stops being one under the single edit nothing else here catches: narrowing or deleting the row. A vanished root is loud; a row someone removed leaves nothing behind to be loud about, and the published skills tree would walk off the surface silently. That case is pinned.content/docs— deliberately not a coverage tree, and this is the one judgement call in the change. Same mechanism, different claim: this gate's roots are the agent-facing surface (没有任何门禁校验文档里 fenced shell 示例的可执行性:实测 shell 转义泄漏进代码块对全部门禁不可见 #5151 ruled it, check-shell-escape-residue lost the same two guides #7251 moved — itsskillsroot does not reach.claude/skills#7403 widened it), andcontent/docsis published prose that happens to share the scan. Declaring coverage over it asserts a completeness promise about the docs tree that no card has made. Filed as its own finding rather than decided here.⛔ No allowlist and no per-file opt-out: the two ways to clear a finding are to move the document under a declared root, or to declare a root for it — both the deliberate decision the gap deserves.
Reverse verification
On disk, against the real gate.
.claude/agents/reviewer.md— the card's literal scenario — planted with a fencedbashblock, inside a trap:Ablation of the implementation. Committed first, then the detection line mutated on disk (
const missing = documents.filter(…)→[]), verified landed by marker count and by a blob-hash mismatch againstHEAD, then restored:The four that go red are the coverage ablation, the row-deletion case, the same-walk case, and the pre-existing out-of-scope case whose fixture already contained a
.claude/hooks/notes.mdnobody was reporting. The⭐ REDS on this card's own scenariocase also asserts that every other signal in the same run reads healthy — nounresolved, novacuous,.claude/skillsstill returning its file, the planted residue not even judged. Without that half the case would pass for a gate that reddened on anything.Gates (all at
150ff53, exit codes captured by redirect-then-capture)node scripts/check-shell-escape-residue.mjsOK (5/5 root(s) resolved …; coverage -- .claude: 4/4; skills: 16/16 …)pnpm exec vitest run scripts/__tests__/check-shell-escape-residue.test.tsTest Files 1 passed (1) / Tests 43 passed (43)pnpm exec vitest run scripts/__tests__/Test Files 107 passed (107) / Tests 3253 passed (3253)pnpm type-check:scriptspnpm lint:root32 problems (0 errors, 32 warnings), none in the two changed filespnpm check:control-bytesscanned 6440 tracked text file(s); plus a direct control-byte grep over both files: cleannode scripts/check-changeset-presence.mjsNo source or published contract of a released package changed in this range, so no changeset is owednode scripts/check-governed-queue-guard.mjs --test THE_2_CHANGED_PATHSNOT GOVERNED — 2 path(s) checked against 5 governed surface(s); none matchedReaders
git grep -l check-shell-escape-residue -- scripts/ .github/returns exactly three files: the gate, its test, and.github/workflows/shell-escape-residue.yml.The workflow needs no
.claudetrigger path — it deliberately has no path filter at all. Its header: "Hence: nopathsand nopaths-ignorehere, deliberately.scripts/__tests__/check-shell-escape-residue.test.tsfails if either is ever added" — and that pin (runs it in NO path-filtered workflow) is still green. It fires on every pull request shape, which is also what makes it requirable.Note on CI
Live E2E (informational)is red on every branch today for an upstream reason (#7990 / objectstack#16186), unrelated to this change.🤖 Generated with Claude Code
https://claude.ai/code/session_01MM7kaS4dPpYHV5BsMyu4tQ
Generated by Claude Code