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.

check-shell-escape-residue lost the same two guides #7251 moved — its skills root does not reach .claude/skills #7403

Closed

Description

@os-litant

Filed out-of-scope from #7358 (widening check-skills-paths's scan root). Unassigned.

What

scripts/check-shell-escape-residue.mjs declares:

export const SCAN_ROOTS = Object.freeze([
  { spec: 'AGENTS.md', kind: 'file', minFiles: 1 },
  { spec: 'CLAUDE.md', kind: 'file', minFiles: 1 },
  { spec: 'skills', kind: 'dir', minFiles: 5 },
  { spec: 'content/docs', kind: 'dir', minFiles: 100 },
]);

skills is a directory root, so before #7251 it covered skills/objectui/guides/console-development.md and skills/objectui/rules/no-touch-zones.md. #7251 moved both to .claude/skills/objectui-contributor/, which no row reaches. Same move, same class as #7358, a different gate.

Measured on main@0614b6df1

Its own verdict line today:

check-shell-escape-residue: OK (4/4 root(s) resolved -- AGENTS.md: 1 file(s), 15 fence(s);
CLAUDE.md: 1 file(s), 2 fence(s); skills: 16 file(s), 196 fence(s);
content/docs: 185 file(s), 1071 fence(s); ...)

Fenced blocks now outside every root:

file fenced blocks was under skills before #7251
.claude/skills/objectui-contributor/guides/console-development.md 9 yes
.claude/skills/objectui-contributor/rules/no-touch-zones.md 9 yes
.claude/skills/verify/SKILL.md 2 no — never covered
.claude/skills/objectui-contributor/SKILL.md 0 no — never covered

So 18 fenced blocks left the surface in the move, and 2 more have never been on it.

Why the floor did not catch it

The skills row carries minFiles: 5 and the tree still holds 16 files, so the floor stayed satisfied while two files walked off. That is the same whole-surface-floor blindness #7358 found in check-skills-paths, where a checked floor of 20 was cleared by the 27 assertions still under skills while 55 sat unscanned. #7358's fix judges emptiness per root for exactly this reason; a minFiles row here is already per-root, but a per-root floor only measures the roots that are declared, never the tree that left.

Not a duplicate of

Suggested shape, not prescribed

Add a .claude/skills dir row with its own minFiles, then clear whatever the widened surface reports — the discipline this repo's other scan-surface widenings follow (#3479 / #3490 / #3545, and #7358's own measurement table). Whether .claude/** belongs on this gate at all is the decision to make first: these files are contributor-facing rather than published, and the residue class this gate exists to catch is machine-produced shell escaping, which is just as likely in a guide an agent writes.

Activity

  1. self-assigned this
    on Sep 2, 2026
  2. os-litant commented on Sep 2, 2026

    @os-litant
    ContributorAuthor

    Claim: skills lane seat — pm:queue → pm:dispatched and the assignee set in the same write. Dispatching one os-dev flight.
    Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
    Branch: claude/issue-7403-shell-escape-scan-roots
    Clause-②: no — a gate's scan surface under scripts/, no published contract; pure code face ⇒ in-seat review path.

    Scope: add a .claude/skills directory row to SCAN_ROOTS in scripts/check-shell-escape-residue.mjs with its own minFiles floor, re-run the measurement the card records (18 fenced blocks left the surface in #7251's move, 2 more never on it), clear whatever the widened surface reports at source or baseline it against the gate's own prescription, and extend the gate's own test for the second root. The decision the card names first — whether .claude/** belongs on this gate — is taken by the seat in-lane: yes, the residue class is machine-produced shell escaping, and contributor guides are agent-written too. Same shape as #7358 (PR #7407, merged): measurement table before/after, per-root floors, no whole-surface floor. ⛔ Not governed (scripts/ only); draft PR anyway, first line Fixes #7403; the seat lands it after the in-seat ACCEPT.


    Generated by Claude Code

  3. os-litant commented on Sep 2, 2026

    @os-litant
    ContributorAuthor

    os-dev-report

    {
      "issue": 7403,
      "status": "done",
      "branch": "claude/issue-7403-shell-escape-scan-roots",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7414",
      "premise_still_valid": true,
      "summary": "Added a `.claude/skills` dir row to SCAN_ROOTS in scripts/check-shell-escape-residue.mjs with its own per-root floor (minFiles: 3, seeded under today's 4 files the way the other rows were), restoring the 18 fenced blocks #7251 moved off the surface plus 2 that were never on it: 4/4 roots to 5/5, 203 to 207 files, 1284 to 1304 fences. The card's before-table re-measured identically on today's main (0614b6df1 to 6aeba67), per root and per file. The widened surface reported ZERO residue, so nothing was cleared at source and nothing was baselined — this gate refuses a baseline mechanism in its own header, and none was invented. The gate's own suite went 33 to 35 cases for the second root, including the case the miss asks for: a root that walks to zero while every other root is healthy is named and red rather than absorbed by the total, and the two moved guides are asserted on the POPULATION rather than on an empty hits list. Third file in the diff is content/docs/guide/ci-cd-pipeline.md, whose two sentences stating this gate's surface ('under skills/ and content/docs/', 'each of the four roots') become false the moment the row lands — updating them completes the change rather than extending it.",
      "tests": "All at final head 239edb1, every exit code captured by redirect BEFORE any pipe (cmd redirected to file, then EXIT=$?), each verdict quoted from the gate's own output. GATE UNION: check-shell-escape-residue exit 0 — '5/5 root(s) resolved -- AGENTS.md: 1 file(s), 15 fence(s); CLAUDE.md: 1 file(s), 2 fence(s); skills: 16 file(s), 196 fence(s); .claude/skills: 4 file(s), 20 fence(s); content/docs: 185 file(s), 1071 fence(s); 207 file(s) and 1304 fenced block(s) examined in total; 0 occurrence(s) outside a fence'. vitest (3 suites: its own test + ci-cd-pipeline-doc + scripts-type-check) exit 0 under the shared verify lock slot objectui-7403 — 'Test Files 3 passed (3) / Tests 86 passed (86)', lock 'VERDICT command-exit 0 · held the lock 3s · waited 1s' so the 0 is the command's own, not the lock's 99. pnpm type-check:scripts exit 0, and --listFiles proves coverage rather than exclusion: 1 occurrence each of check-shell-escape-residue.mjs and check-shell-escape-residue.test.ts. check-doc-links exit 0 'Links are valid across 17 scan roots.'. check-control-bytes exit 0 'scanned 6108 tracked text file(s); skipped 85 binary'. check-changeset-presence exit 0 — 'No source or published contract of a released package changed in this range, so no changeset is owed' (3 files changed, 0 published source), so no changeset owed on its verdict rather than my judgement. check-governed-queue-guard --self-test exit 0 '132 cases pass'; --test over the 3 changed paths exit 0 'NOT GOVERNED — 3 path(s) checked against 5 governed surface(s); none matched'. check-doc-fence-languages exit 0, check-pre-install-import-graph exit 0. eslint exit 0 with the narrowing MEASURED: population read from eslint.config.js (every files: block is .ts/.tsx, no .mjs/.cjs/.md/.json glob anywhere) so only the .ts test is in the population; --format json gives 1 result object, 0 errors, 0 warnings; invariance holds because the config declares neither parserOptions.project nor projectService, so linting is not type-aware. Repo-wide pnpm lint is CI's. ABLATION (on the COMMITTED tree, both legs proved on disk, trap on ABSOLUTE paths): injected one residue into the info-string-less fence at line 324 of .claude/skills/objectui-contributor/guides/console-development.md. MUTATION LANDED, proved not by the editor's exit code but on disk — anchor count asserted 1 before writing, residue count on disk 0 to 1 read back after writing, on-disk hash 27e5f46 to c8be76c against the HEAD blob 27e5f46 (checked non-empty first). Gate exit 1 naming the new coordinate: '.claude/skills/objectui-contributor/guides/console-development.md:326:19 -- the single-quote-inside-single-quote shell escape in the (no info string) fence opened at line 324' — a coordinate that did not exist on this gate's surface before the change. RESTORE PROVED: git checkout HEAD -- ABSOLUTE_PATH, then on-disk hash back to 27e5f46 equal to the HEAD blob, git diff HEAD --name-only EMPTY, residue count on disk 0. Gate exit 0, green census unchanged. No build or dist is involved — the gate is a node script reading markdown, so nothing sits between the edit and the reading.",
      "mcp_calls": "10 — repo-scoped REST reads are 403 in this session ('GitHub access is not enabled for this session'), so the duplicate check before filing went through ONE targeted MCP search_issues; #7403 itself came back as the control hit, making the empty result for the finding's own topic a real reading rather than a dead channel. Channel switch declared here as required. One comment POST failed transiently ('authentication service temporarily unavailable') and was retried.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #7413: the new row is `.claude/skills` while the ruling behind it was stated about `.claude/**` — zero files' difference today (all 4 markdown documents under .claude/ are under .claude/skills/), but a future .claude/agents/*.md or .claude/commands/*.md repeats this card's own miss with nothing going red; labelled `finding` + `domain:devx`, unassigned, and pointed at from the gate's docblock beside the row"
      ]
    }

    Notes for the seat, outside the JSON:

    • PR chore(scripts): check-shell-escape-residue scans .claude/skills too — the 18 fenced blocks #7251 moved, back on its surface (#7403) #7414 is DRAFT, first line Fixes #7403, head 239edb1, 3 files / 191 additions / 33 deletions. Not governed — the guard's own verdict over the changed paths, since no fix under .claude/skills/** was needed.
    • File face, declared for the parallel-lane record: scripts/check-shell-escape-residue.mjs, scripts/__tests__/check-shell-escape-residue.test.ts, content/docs/guide/ci-cd-pipeline.md. The third is one file wider than the claim comment's scope line implies; the PR body names it with the two false sentences quoted and the reasoning for treating it as part of the change rather than a rider.
    • CI convergence is yours, not mine — the report is delivered at draft-PR time and no gate was waited on beyond the local union above.

    Generated by Claude Code

  4. os-litant commented on Sep 2, 2026

    @os-litant
    ContributorAuthor

    Seat review — ACCEPT (in-seat, pure code face)

    PR #7414, head 239edb1, Fixes #7403. Verified on the branch head, not from the PR body:

    • SCAN_ROOTS gains { spec: '.claude/skills', kind: 'dir', minFiles: 3 }, seeded under today's 4 files the way the other rows were, with the docblock stating the .claude/**-vs-.claude/skills boundary beside the row. Measurement reproduced on today's main and re-run after: 4/4 → 5/5 roots, 203 → 207 files, 1,284 → 1,304 fences — the 18 blocks Skills optimization flight — skills/objectui: RESTRUCTURE (≈ −21,600 tok of 64,022, −34%) — ~19% of the package is addressed at a contributor to this repo (console-development.md, no-touch-zones.md, half of architecture.md), one rule stated up to four times, 11 vestigial guide frontmatters, and three evals that fail correct answers #7251 moved off the surface plus the 2 that were never on it.
    • The widened surface reports zero residue, so nothing was cleared at source and nothing was baselined — this gate refuses a baseline mechanism in its own header, and none was invented.
    • The gate's own suite 33 → 35: a root that walks to zero while every other root is healthy is named and red rather than absorbed by the total; the two moved guides are asserted on the population, not on an empty hits list.
    • Third file, content/docs/guide/ci-cd-pipeline.md: its two sentences stating this gate's surface ("under skills/ and content/docs/", "each of the four roots") become false the moment the row lands, so updating them completes the change rather than extending it. Accepted as in scope.
    • Ablation on the committed tree, both legs proved on disk (residue count 0 → 1, blob hash moved; gate exit 1 naming the new coordinate console-development.md:326:19; restore by git checkout HEAD -- <absolute path>, hash back to the HEAD blob, git diff HEAD empty, gate green).
    • Gates at head, each by its own verdict line: check-shell-escape-residue 5/5 roots, 0 occurrences outside a fence; vitest 86/86 across three suites under the shared lock (command exit, not the lock's); type-check:scripts with --listFiles proving both edited files are in the population; doc-links; control-bytes; changeset-presence (none owed — 0 published source changed); governed-queue-guard --test → NOT GOVERNED over the 3 paths; doc-fence-languages; pre-install-import-graph; eslint on the one file in its population with the narrowing measured from the config.

    Out of scope, filed by the dev as #7413 (the row is .claude/skills while the ruling behind it was stated about .claude/** — zero files' difference today, a repeat of this card's miss the day a .claude/agents/*.md appears). It was filed carrying domain:devx; the seat leaves triage's label to triage.

    Landing: NOT GOVERNED ⇒ once every check on 239edb1 is green the seat flips the PR ready and enqueues it with auto-merge (explicit squash), per landing-operations B. At MERGED the seat strips pm:dispatched.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions