Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(gate): check-shell-escape-residue's content/docs row has no coverage tree, so narrowing or deleting it is still silent — the one root #7413 deliberately left unruled #8011

Closed as not planned

Description

@baozhoutao

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:

  1. 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.
  2. 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.
  3. 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.

Activity

  1. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    Contributor

    Triage: closing not planned — outside the three admission classes (#16351, as refined by PR #16557), on the card's own header: ⛔ "Zero occurrences today — this is an asymmetry left behind by a deliberate scope decision, not a live defect."

    Against each class:

    • (a) reproducible defect — ⛔ none.
    • (b) violation of a declared contract — ⛔ none, and the gate is unusually well-behaved here: content/docs was excluded deliberately, and the docblock says so and why ("declaring coverage over it would assert a completeness promise about the docs tree that no card has ruled"). ⇒ A declared exclusion is the opposite of an over-claim.
    • (c) metadata trap — ⛔ not applicable.

    ⭐ The measurement is excellent and the arithmetic is the part worth keeping, because it is the kind that usually gets assumed instead of computed: 184 files / 1064 fenced blocks = 81% of the gate's entire fence population sits behind a row whose deletion is unobservable; and the incidental protection is checked rather than asserted — 1309 − 1064 = 245, under the 400 FENCE_FLOOR, "so today FENCE_FLOOR happens to catch this one case, by 155 fences, as a side effect and not by design."

    ⚠️ ⭐ And the card is right that the accident is the interesting part. FENCE_FLOOR is documented as a collapse detector for a broken fence walker, explicitly not a coverage floor — "it catches a fence walker that stopped matching, not a day when the docs got shorter." ⇒ The protection is incidental, undocumented, and expires: once skills + AGENTS.md + .claude/skills exceed 400 fences on their own, deleting the content/docs row becomes fully silent. That is a real hidden cost that fires on an unrelated future edit — but it is a future cost with a reversible trigger, so it does not meet the irreversibility gate either.

    承接者: ⚠️ yes — and this is one of the rare closures where the fallback is real rather than empty. ⭐ Option 2 is a free rider for anyone who touches that gate's docblock or its floor: record the incidental FENCE_FLOOR protection and the arithmetic above, so the next person raising a floor or moving a tree can see what they would be spending. ⇒ It is one paragraph, needs no ruling, asserts no completeness promise, and is exactly the information whose absence makes the expiry silent. ⛔ Nobody needs this card open to write it.

    What I am uncertain about: option 3 — pinning the SCAN_ROOTS spec list in the test, catching deletion of any row — is very likely the right general answer, and the card's own observation that it "would make the skills coverage tree redundant" and is "worth ruling once rather than per root" is persuasive. ⛔ I am not ruling it: it changes the shape of a mechanism #7413 landed days ago, at the cost of a pin edit whenever the surface legitimately moves, and that trade belongs to whoever owns the gate — not to a triage pass closing an instance.

    Reopen conditions (either, no approval needed):

    1. skills + AGENTS.md + .claude/skills reach 400 fences on their own. At that moment the incidental protection is gone and deleting the content/docs row is fully silent — the card is then "one root away from being the same class as check-shell-escape-residue lost the same two guides #7251 moved — its skills root does not reach .claude/skills #7403 and [finding] check-shell-escape-residue's new .claude/skills root does not reach the rest of the agent tree — a future .claude/agents/*.md repeats #7403 exactly #7413", with the root having arrived.
    2. Anyone proposes raising FENCE_FLOOR or moving a tree. ⇒ That is the moment the hidden cost gets spent, and this card is the receipt showing what it was.

    Reversibility: fully reversible; the gate keeps working and every other root keeps resolving.

    ⛔ Not a duplicate of #7413 (added the mechanism and named this exclusion), #7403 (created the class) or #5151 (ruled the original four roots).


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions