Skip to content

pnpm check:nul-bytes enumerates git ls-files, so a brand-new file reads green locally until it is staged #6984

Description

@os-project-manager

Found while implementing #6728 (PR pending). Out of scope there — filed unassigned.

Observation

scripts/check-nul-bytes.mjs builds its scan set from tracked files only:

const files = execFileSync('git', ['ls-files', '-z'], {});   // line 416

git ls-files with no --others lists the index, so a file that has been
written but not yet git add-ed is not scanned at all. The gate then exits
0 and prints its usual success line, which reads as "this tree has no raw
control bytes" rather than "the file you just wrote was not looked at".

Measured (this worktree, origin/main @ 73bff86 + one new untracked file)

A new test file had a raw 0x1b (ESC) materialized into it at write time —
the accident AGENTS.md describes, where an editing tool turns the escape text
into the byte precisely when you are writing about control characters:

$ node scripts/check-nul-bytes.mjs > /dev/null 2>&1; echo "exit=$?"
exit=0                       # untracked — not scanned

$ grep -naoP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' packages/cli/test/<new file>.ts | od -c
0000000   2   2   0   :  033  \n     # the byte was there the whole time

$ git add -A && node scripts/check-nul-bytes.mjs > /dev/null 2>&1; echo "exit=$?"
exit=0                       # after the byte was removed; staging is what makes it visible

The self-scan in AGENTS.md (grep -naP …) found it; the gate did not, and the
only difference was the index.

Why it is finding and not a defect

CI is unaffected: a PR's tree is fully tracked by the time any workflow runs,
so the gate's coverage there is complete, and #5157's widened byte surface
works exactly as documented. The harm is local and one-directional — the run
AGENTS.md tells you to make before pushing ("Run node scripts/check-nul-bytes.mjs
before pushing") is the run most likely to be looking at a file that is new,
i.e. the one case the gate skips. So the pre-push check is green for a reason
unrelated to the bytes, on exactly the file most likely to carry one, since a
newly-authored file is where an editing tool materializes an escape.

Possible shapes (not a recommendation — this needs the gate owner)

  1. Add --others --exclude-standard to the ls-files call so untracked,
    non-ignored files are scanned too, and say so in the gate's summary line.
  2. Leave enumeration alone and have the gate report the count it skipped
    ("N untracked file(s) not scanned — stage them or pass --all"), so the
    green is honest about its scope.
  3. Do nothing and treat the AGENTS.md self-scan as the pre-push instrument,
    with the gate scoped to committed state by design.

Shape 1 changes what a bare local run means; shape 2 keeps the gate's contract
and closes the false-confidence half only. Either way the two-line self-scan in
AGENTS.md stays the belt to this gate's braces.

Refs #4890, #5140, #5157.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions