Repository navigation
Description
Measured on HEAD 8086c717 (.gitattributes 45 lines, identical to origin/dev) while verifying an unrelated Step 0 post-condition for the 2026-08-21 E2E series.
.gitattributes pins 173 tracked files to eol=lf. Four of them have a non-LF index blob, meaning the rule was added without renormalizing. No existing gate detects this.
Measured
git ls-files --eol | grep 'attr/text eol=lf' | grep -v 'i/lf'i/none w/none attr/text eol=lf docs/.astro/content-assets.mjs
i/none w/none attr/text eol=lf docs/.astro/content-modules.mjs
i/mixed w/mixed attr/text eol=lf docs/src/pages/rss.xml.js
i/crlf w/crlf attr/text eol=lf packages/squad-cli/src/remote-ui/app.js
Census: pinned=173, i/lf=169, not-i/lf=4, sum 173 — no file uncovered by the partition.
docs/src/pages/rss.xml.js is the same file that carries exactly one CRLF line in the working tree (documented in the E2E runbook). This is the index-side instance of it.
Why no gate catches these
scripts/check-shebang-eol.mjs is green (exit=0) and honest about why. Its selector is shebang detection, not .gitattributes:
Shebang EOL check passed: all 45 shebanged files are pinned to LF.
45 files checked, not 173. The script's own header documents the boundary explicitly:
SCOPE BOUNDARY -- read this before assuming a green run means "no EOL bugs". There are two CRLF failure classes and this lint covers exactly one:
- STRICT PARSE (covered). ...
#!is a cheap, exact, static signature, so the check is sound and has no false negatives.- TOOL REWRITE (NOT covered). ... Class 2 has no cheap static signature ... Extending this lint to guess would trade a sound check for an unsound one, so the boundary is deliberate.
That is a correct and deliberate scope decision — this issue is not a bug report against that lint. The lint's header even names the exact state these four files are in:
a rule that was added without renormalizing is still a broken repo
...and then correctly scopes itself out of finding it for non-shebanged files. The gap is real; the lint declining to cover it is not the defect.
Impact
packages/squad-cli/src/remote-ui/app.js is source, committed CRLF, pinned LF. Any tool that rewrites it with LF leaves the tree perpetually dirty, which is the precondition for a broad git add sweeping in unrelated changes -- the failure mode the pinning was introduced to prevent.
The two docs/.astro/*.mjs files are generated build output at i/none w/none; whether they should be tracked at all is a separate question.
Suggested fix
git add --renormalize docs/.astro/content-assets.mjs \
docs/.astro/content-modules.mjs \
docs/src/pages/rss.xml.js \
packages/squad-cli/src/remote-ui/app.js
git commitThen verify the partition returns empty:
git ls-files --eol | grep 'attr/text eol=lf' | grep -v 'i/lf'Suggested gate
The partition above is a complete, cheap, sound check over the pinned set and has no false negatives -- a pinned file either has an LF index blob or it does not. It complements the shebang lint rather than extending it, so it does not trade soundness for coverage:
Every path with
attr/text eol=lfmust reporti/lf.
Note grep -v 'i/lf' rather than grep 'i/crlf' -- the tree contains i/mixed and i/none cases that an i/crlf-only filter misses. Three of the four above would be missed.
Tree-dependence -- read before reproducing
Do not treat the file list or the count as fixed. A second worktree at the same 45-line .gitattributes generation reported pinned=92 with a disjoint 3-file set (docs/pagefind.yml, samples/storage-provider-azure/scripts/{create,delete}-storage.sh), all three of which are i/lf w/lf clean here. Index state is per-worktree.
Pair any reported count with git rev-parse --short HEAD and the worktree, per the existing convention.
Not claimed
- Not measured: how each blob came to be committed non-LF, or in which commit.
- Not measured: whether
docs/.astro/should be tracked. - Not blocking the 2026-08-21 E2E series. Pre-existing, unrelated to the wave.
Activity
bradygaster commentedon Aug 21, 2026
Retracted — all four cited files were measured from a stale tree. Zero real instances.
Closing. The measurement in the issue body is wrong on all four paths, in two distinct ways. Recording the correction rather than deleting it, because the failure mode is the point.
Root cause:
HEADwas 6 commits behindorigin/devat filing time8086c717onbradygaster-gh-aw-triage-pre-e2e, 6 behind. I never fetched before measuring.Two of the four were never defects
Both
docs/.astro/*.mjsfiles contain no line endings at all.i/noneis the correct and expected report for a newline-free file —eol=lfon it is vacuous. These are not renormalization candidates and never were.This is a false-positive class in the proposed filter. The issue body suggested
grep -v 'i/lf'specifically to catchi/mixedandi/nonethat ani/crlf-only filter would miss.i/mixedis a real defect state;i/noneis not. The correct filter is:Measured on the same tree:
-v i/lfreturns 4,i/(crlf|mixed)returns 2. The two it drops are the vacuous ones.The other two are already renormalized on
devorigin/devblobdocs/src/pages/rss.xml.js75bae9427073178apackages/squad-cli/src/remote-ui/app.js5000d0c61521e5f2Both differ from
HEADand both dev blobs are clean. They were fixed in commits this worktree did not have. Blob extraction done viacmd.exeredirection rather than a PowerShell pipe, which re-encodes.Also incorrect: the tree-dependence note
The body cites a second worktree at
pinned=92and calls it "the same 45-line.gitattributesgeneration." It was not — that worktree was 13 commits behind at a 27-line generation. Meanwhile:Byte-identical. The
92 vs 173spread is explained entirely by stale checkouts, not by the pinning definition moving. Line-counting the file through a pipe produced a third number; hashing the blob is decisive and should have been the instrument from the start.What survives
The gate proposal is still cheap and sound — every path with
attr/text eol=lfmust reporti/lfori/noneis a complete partition over the pinned set with no false negatives. But it now has zero known instances: three candidates found on another branch were renormalized by #1790, two here were renormalized ondev, and two were never defects.A gate proposal with no instance is speculative, so this closes rather than staying open as backlog. If a real
i/(crlf|mixed)pinned file appears ondev, reopen withgit rev-parse --short HEADbeside it.The scope-boundary analysis of
scripts/check-shebang-eol.mjsin the body is accurate and unaffected — that lint's shebang-only selector is deliberate and correctly documented in its own header. Nothing here is a defect in it.Filing rule this cost
Every count in the original body was real output from a real command. None of them measured the repository.
git fetchplus the blob SHA of the file defining the set were each one command away.