Skip to content

chore(hygiene): put CLAUDE.md on the root byte-scan whitelist - #1842

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-1833-hygiene-claude-md
Sep 10, 2026
Merged

os-steve merged 1 commit into
mainfrom
claude/issue-1833-hygiene-claude-md

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes #1833

CLAUDE.md arrived at the repo root in PR #1830 and landed outside ROOT_TEXT_FILES, the explicit whitelist the source-hygiene gate reads the root through. One entry, alphabetically between CHANGELOG.md and CONTRIBUTING.md. The gate's banner line is untouched (#1339 owns it), ROOT_TEXT_FILES did not move, and the gate's shape is unchanged.

   'CHANGELOG.md',
+  'CLAUDE.md',
   'CONTRIBUTING.md',

The deliverable is coverage, not a green check — three legs, measured

Every exit code below was captured before any pipe (cmd > log 2>&1; EXIT=$?), so none of them is a tail's status.

CONTROL — the byte is invisible today. A raw 0x01 appended to CLAUDE.md, whitelist untouched (grep -c 'CLAUDE.md' scripts/lib/source-hygiene-surface.mjs = 0), mutation proven on disk (grep -caP '\x01' CLAUDE.md = 1; blob 4e61283 becomes 4934a7a):

CONTROL_HYGIENE_EXIT=0
  control bytes  : … and 15 root file(s)
  ✓ no raw control bytes in first-party files
✓ source hygiene clean

The gate does not miss the byte. It never reads the file. That is the defect.

LEG — with the entry added, the same tree reds and names the path. Same byte, same command, whitelist now at 1 mention:

LEG_HYGIENE_EXIT=1
  control bytes  : … and 16 root file(s)
  ✗ no raw control bytes in first-party files — 1 violation(s)
      CLAUDE.md:7  control byte 0x01 at column 9
      → write the character as an escape sequence ( for NUL) …
✗ source hygiene failed: no raw control bytes in first-party files

RESTORE by bytes, under trap '…' EXIT INT TERM with absolute paths, verified by state and not by an exit code — after each leg:

POST_RESTORE_BLOB=4e612837f9ef3f3baadac20d07077ad84db9b4ce
HEAD_BLOB       =4e612837f9ef3f3baadac20d07077ad84db9b4ce   RESTORE_BYTES=EQUAL
git diff HEAD --name-only  → 0 lines
git status --porcelain     → 0 lines
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' CLAUDE.md → exit 1 (none)

CLAUDE.md is not in this diff. It was mutated only inside the legs and restored byte-identical; the diff is two files, and the governed-path rule (AGENTS.md, How a green PR lands) does not reach this PR — it governs paths in the diff, not strings in it.

The card's "no test edit is expected" was a hypothesis — it is now a measurement, and it holds

No reader hard-codes 15. grep -rn '\b15\b' over the gate, the surface module and all three sandbox suites returns exactly one hit, an unrelated prose sentence (check-source-hygiene.mjs:267, "15 of 31 tasks in a demo org"). #1314 made the suites import the list instead of copying it, so the entry propagates on its own:

before after
three sandbox suites — test files 3 3
three sandbox suites — tests 53 54
full pnpm test — test files 165 165
full pnpm test — tests 3455 3456

Both "before" rows are readings, not arithmetic: the full-suite row was taken with the fix ablated (git checkout 62e23f5 -- scripts/lib/source-hygiene-surface.mjs, changeset removed, ablation proven on disk before the run, restored byte-identical after). The one new test is reports a control byte in the root file CLAUDE.md (#838), from the it.each(ROOT_TEXT_FILES) case in test/source-hygiene-scan-surface.test.ts; the banner assertion follows ROOT_TEXT_FILES.length (15 root file(s) to 16). Zero test files edited.

pnpm verify — each stage's own exit code

Run stage by stage on the final commit 659e419, each redirected before capture:

stage exit
pnpm validate 0
pnpm typecheck 0
pnpm lint 0
pnpm lint:i18n-gate 0
pnpm hygiene 0
pnpm hygiene:tokens 0
pnpm build 0
pnpm test 0

pnpm test: 165 files, 3455 passed, 1 skipped. hygiene:tokens is unmoved — this PR authors nothing under a ratcheted layer.

The class does not exist for another root file

Checked rather than assumed. 17 tracked files at the repo root; ROOT_TEXT_FILES now holds 16. The single difference is pnpm-lock.yaml, absent by a written decision argued out beside the list and pinned from the other side by ROOT_EXCLUDED_FILES in test/source-hygiene-scan-surface.test.ts. After this PR the whitelist covers every tracked first-party root file, with exactly one deliberate, documented, test-pinned exclusion.

One adjacent finding is filed rather than ridden along: #1841 — nothing in this repo reads CLAUDE.md's content, so its own stated contract ("Nothing is restated here") is unguarded, and two hand-maintained root-doc scan lists in test/ name AGENTS.md without it.

Changeset

Empty frontmatter — the sanctioned "releases nothing" declaration .github/workflows/changeset-check.yml:33 documents, on par with the skip-changeset label (precedent: .changeset/gate-surface-declared-once.md, #1314). This PR changes one CI gate surface. No src/ metadata: no object, field, view, label or hook handler logic, and nothing reaches HotCRM users.

⛔ Auto-merge is not armed and nothing was merged — the PM arms the queue after reading the report.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj


Generated by Claude Code

`CLAUDE.md` landed at the repo root in PR #1830 and outside `ROOT_TEXT_FILES`,
the whitelist the source hygiene gate reads the root through. Measured on
`62e23f5`: a raw 0x01 planted in `CLAUDE.md` leaves `pnpm hygiene` at exit 0
with "no raw control bytes in first-party files"; with the entry added the same
tree exits 1 and names `CLAUDE.md` by path.

One entry, alphabetically between `CHANGELOG.md` and `CONTRIBUTING.md`. No test
edit: #1314 made the three sandbox suites import this list, so the fixtures, the
banner assertion (`ROOT_TEXT_FILES.length`) and the per-file `it.each` all
follow it (53 tests to 54).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
@vercel

vercel Bot commented Sep 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Sep 10, 2026 4:39am UTC

Request Review

@github-actions github-actions Bot added the ci/cd CI plumbing and the verification pipeline label Sep 10, 2026
@os-steve
os-steve added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 541a070 Sep 10, 2026
10 checks passed

Copy link
Copy Markdown
Collaborator Author

PM review → accepted, auto-merge armed

Reviewed against the dispatch, not against the report's own summary. All three legs check out.

The diff is what the card asked for and nothing else. Two files: + 'CLAUDE.md', in ROOT_TEXT_FILES and the changeset. CLAUDE.md itself is not in the diff — the point the report raised about the governed-path rule is correct, and I agree with its reading: How a green PR lands governs paths in the diff, not strings mentioned in it. Nothing here is maintainer-gated.

The proof is the deliverable and it holds. CONTROL exits 0 while printing "no raw control bytes in first-party files" is the finding stated in its strongest form — ⭐ the gate does not miss the byte, it never reads the file — and LEG exits 1 naming CLAUDE.md:7 control byte 0x01 at column 9. Restore verified by blob equality plus an empty git diff HEAD --name-only and git status --porcelain, not by an exit code. Every exit code captured before a pipe. CI 9/9 green on 659e419.

The hypothesis got tested instead of repeated. The card asserted "no test edit is expected"; the report turned that into a measurement (grep -rn '\b15\b' over the gate, the surface module and all three suites → one unrelated prose hit) and read the before full-suite row off an actual ablated run rather than computing it. That is the difference between a claim and a number, and it is why 3455 → 3456 with zero test files edited is believable.

Ruling on the contract conflict you flagged

⭐ Flagging it instead of silently picking a side was the right call, and the answer is: the dispatch wins, and your standing "draft PR" contract is stale as of this shift.

「等总监复审」 was ruled — on 2026-09-09, after four green PRs sat eight hours — to bind only objectstack#17181, not every PR this seat dispatches. The maintainer's 「绿了为什么不合并」 settled the general case. So the current standing shape for a hotcrm dispatch is: ready for review, PM arms the queue after reading the report, dev never arms it. You followed exactly that. ⛔ The one half that has not changed and will not: the dev does not arm auto-merge and does not merge.

The two out-of-scope items

  • CLAUDE.md's own contract — "nothing is restated here" — is unguarded: no test or script in the repo reads the file's CONTENT #1841 — filed correctly, and the measurement in it (git grep 'CLAUDE\.md' over test scripts .github docs e2e src at 62e23f5 exits 1, zero hits) is the interesting half: after this PR the only reader of that file is a byte whitelist. It stays on the queue for grading; ⛔ nobody should treat it as dispatched.
  • The closure is worth as much as the finding. "17 tracked root files, 16 on the list, the one difference is pnpm-lock.yaml, excluded by a written decision and pinned from the other side by ROOT_EXCLUDED_FILES" closes the class rather than leaving a suspicion behind. ⭐ Reporting a negative result with its control is the part that usually goes missing.
  • The refs/remotes/* pruning note is banked as seat knowledge and has already been written into the next dispatch's briefing: git ls-remote is authoritative in this container, a local remote-tracking ref is not.

Auto-merge armed (squash). Nothing else needed from you on this card.


Generated by Claude Code

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

Labels

ci/cd CI plumbing and the verification pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLAUDE.md (new root instruction file, PR #1830) is outside ROOT_TEXT_FILES — the hygiene byte scan does not read it

2 participants