Filed out-of-scope from #7413 (PR #8010). Unassigned. Zero occurrences today — this is an asymmetry left behind by a deliberate scope decision, not a live defect.
What
#7413 added COVERAGE_TREES to scripts/check-shell-escape-residue.mjs: trees in which every .md/.mdx must be reached by some SCAN_ROOTS row, red by file name otherwise. Two trees were declared:
export const COVERAGE_TREES = Object.freeze([
{ spec: '.claude', kind: 'dir' },
{ spec: 'skills', kind: 'dir' },
]);
content/docs was deliberately not declared, and the gate's docblock says so and why:
⛔ content/docs is deliberately NOT a coverage tree. Same mechanics, but a different claim: this gate's roots are the agent-facing surface (objectui#5151 ruled it, objectui#7403 widened it), and content/docs is published prose that happens to share the scan. Declaring coverage over it would assert a completeness promise about the docs tree that no card has ruled.
The asymmetry that remains
The skills coverage tree was included even though it is a tautology today (its row is the whole tree, 16 of 16), because a tautology is exactly what it stops being under the one edit nothing else catches: narrowing the row to a subtree, or deleting the row. Neither shows up as unresolved — a root that vanishes from disk is loud, a row someone removes from the declaration list leaves nothing behind to be loud about — and both walk a whole tree off the scan surface silently.
content/docs carries that exposure identically and now has no guard against it. Measured on e1545cf: 184 files, 1064 fenced blocks, i.e. 81% of this gate's entire fence population sits behind a row whose deletion is unobservable. The other four roots would keep resolving, every remaining minFiles floor would be met, and FENCE_FLOOR at 400 is cleared by skills alone (210) plus AGENTS.md (15) only in combination — so it is worth measuring rather than assuming: 1309 - 1064 = 245, which is under the 400 floor. So today FENCE_FLOOR happens to catch this one case, by 155 fences, as a side effect and not by design.
That accident is the interesting part. FENCE_FLOOR is documented as a collapse detector for a broken fence walker, explicitly not a coverage floor:
the floor is set far under that on purpose -- it catches a fence walker that stopped matching, not a day when the docs got shorter.
So the protection is incidental, undocumented, and disappears the moment either tree grows: once skills plus AGENTS.md plus .claude/skills exceed 400 fences on their own, deleting the content/docs row becomes fully silent. It is one root away from being the same class as #7403 and #7413.
The decision to make
Not mechanical — it is the same choice #7413 framed and declined to make unilaterally:
- Declare
content/docs a coverage tree. One row; green by construction today (its row is the whole tree, exactly like skills). Cost: the gate then asserts a completeness claim over published docs, which is a promise about a surface no card has ruled it should make.
- Leave it, and record the incidental
FENCE_FLOOR protection in the gate's docblock along with the arithmetic above, so the next person to raise a floor or move a tree can see what they would be spending.
- Rule that row deletion should be caught generally rather than tree by tree — e.g. pin the
SCAN_ROOTS spec list in the test, which catches deletion of any row including this one, at the cost of an edit to the pin whenever the surface legitimately moves.
Option 3 is arguably the general form of the general form, and would make the skills coverage tree redundant. Worth ruling once rather than per root.
Not a duplicate of
Measured by the dev seat on #7413 while writing the exclusion into the docblock, unclaimed.
Filed out-of-scope from #7413 (PR #8010). Unassigned. Zero occurrences today — this is an asymmetry left behind by a deliberate scope decision, not a live defect.
What
#7413 added
COVERAGE_TREEStoscripts/check-shell-escape-residue.mjs: trees in which every.md/.mdxmust be reached by someSCAN_ROOTSrow, red by file name otherwise. Two trees were declared:content/docswas deliberately not declared, and the gate's docblock says so and why:The asymmetry that remains
The
skillscoverage tree was included even though it is a tautology today (its row is the whole tree, 16 of 16), because a tautology is exactly what it stops being under the one edit nothing else catches: narrowing the row to a subtree, or deleting the row. Neither shows up asunresolved— a root that vanishes from disk is loud, a row someone removes from the declaration list leaves nothing behind to be loud about — and both walk a whole tree off the scan surface silently.content/docscarries that exposure identically and now has no guard against it. Measured one1545cf: 184 files, 1064 fenced blocks, i.e. 81% of this gate's entire fence population sits behind a row whose deletion is unobservable. The other four roots would keep resolving, every remainingminFilesfloor would be met, andFENCE_FLOORat 400 is cleared byskillsalone (210) plusAGENTS.md(15) only in combination — so it is worth measuring rather than assuming: 1309 - 1064 = 245, which is under the 400 floor. So todayFENCE_FLOORhappens to catch this one case, by 155 fences, as a side effect and not by design.That accident is the interesting part.
FENCE_FLOORis documented as a collapse detector for a broken fence walker, explicitly not a coverage floor:So the protection is incidental, undocumented, and disappears the moment either tree grows: once
skillsplusAGENTS.mdplus.claude/skillsexceed 400 fences on their own, deleting thecontent/docsrow becomes fully silent. It is one root away from being the same class as #7403 and #7413.The decision to make
Not mechanical — it is the same choice #7413 framed and declined to make unilaterally:
content/docsa coverage tree. One row; green by construction today (its row is the whole tree, exactly likeskills). Cost: the gate then asserts a completeness claim over published docs, which is a promise about a surface no card has ruled it should make.FENCE_FLOORprotection in the gate's docblock along with the arithmetic above, so the next person to raise a floor or move a tree can see what they would be spending.SCAN_ROOTSspec list in the test, which catches deletion of any row including this one, at the cost of an edit to the pin whenever the surface legitimately moves.Option 3 is arguably the general form of the general form, and would make the
skillscoverage tree redundant. Worth ruling once rather than per root.Not a duplicate of
.claude/skillsroot does not reach the rest of the agent tree — a future.claude/agents/*.mdrepeats #7403 exactly #7413 — added the mechanism and named this exclusion; this card is the exclusion itself.skillsroot does not reach.claude/skills#7403 — the move that created the whole class,.claude/skills.Measured by the dev seat on #7413 while writing the exclusion into the docblock, unclaimed.