What
scripts/check-docs-naming.sh:96 drives the case-collision pass from git ls-files without -z, folds each line with tr, and then compares those folded lines against paths read from a -z listing:
done < <(git ls-files -- docs/) # non-z, quoted
...
done < <(git ls-files -z -- docs/) # -z, raw
Without -z, git quotes any path carrying a character outside the printable ASCII range: an embedded newline, a tab, a non-ASCII byte. Such a path arrives as "docs/a\nb.md", quotes and escape included, while the -z listing yields the raw bytes. The two can never match, so a case collision involving such a path is silently missed.
Found during a fresh-context review of PR #4133, which generalizes this script into plugins/docs-hygiene/skills/generate-file-name-gate/templates/check-file-names.sh.tmpl. The template carries the same code, so every emitted consumer gate inherits it. Pre-existing in the original; the generalization neither introduced it nor made it worse.
Impact
Narrow but real, and exactly where it matters least forgivingly: the collision pass exists because two paths differing only by case cannot coexist on a case-insensitive checkout, so a missed collision is a tree that corrupts on Windows or macOS checkout rather than a style nit. A repository whose docs/ carries a non-ASCII filename gets no protection for it.
Fix shape
Drive both loops from the same -z listing. The outer loop needs the folded duplicates, which sort/uniq -d can still produce from a NUL-delimited stream:
git ls-files -z -- docs/ | tr '\0' '\n' ...
is not the fix (it re-introduces the newline ambiguity). Reading the -z stream into an array once, folding in the shell, and finding duplicates there keeps one source of truth for the path bytes.
Fix both copies together, or land #4139 first so the drift test holds them together afterwards.
What
scripts/check-docs-naming.sh:96drives the case-collision pass fromgit ls-fileswithout-z, folds each line withtr, and then compares those folded lines against paths read from a-zlisting:Without
-z, git quotes any path carrying a character outside the printable ASCII range: an embedded newline, a tab, a non-ASCII byte. Such a path arrives as"docs/a\nb.md", quotes and escape included, while the-zlisting yields the raw bytes. The two can never match, so a case collision involving such a path is silently missed.Found during a fresh-context review of PR #4133, which generalizes this script into
plugins/docs-hygiene/skills/generate-file-name-gate/templates/check-file-names.sh.tmpl. The template carries the same code, so every emitted consumer gate inherits it. Pre-existing in the original; the generalization neither introduced it nor made it worse.Impact
Narrow but real, and exactly where it matters least forgivingly: the collision pass exists because two paths differing only by case cannot coexist on a case-insensitive checkout, so a missed collision is a tree that corrupts on Windows or macOS checkout rather than a style nit. A repository whose
docs/carries a non-ASCII filename gets no protection for it.Fix shape
Drive both loops from the same
-zlisting. The outer loop needs the folded duplicates, whichsort/uniq -dcan still produce from a NUL-delimited stream:is not the fix (it re-introduces the newline ambiguity). Reading the
-zstream into an array once, folding in the shell, and finding duplicates there keeps one source of truth for the path bytes.Fix both copies together, or land #4139 first so the drift test holds them together afterwards.