Context
lib/hook-utils.sh's hook::physical_path canonicalizes a path with realpath (falling back to readlink -f) before the CLAUDE_PROJECT_DIR membership comparison in hook::read_file_path. GNU realpath under Git Bash does not expand Windows 8.3 short names, so a short-form file_path fails the prefix match and the hook exits silently — no lint, no notice, no telemetry.
actionlint hit this and fixed only itself. Its CHANGELOG 0.6.0 (#1133) records: "a short-form file_path (the shape Claude Code's own scratchpad paths take) failed the prefix match and the hook exited silently — no lint, no notice, no telemetry... The hook now parses the path itself... the synced shared lib is untouched for consumers that need the guard."
So the shared lib is still 8.3-blind, and bash-format and markdown-format still consume it. Both document the out-of-project skip as deliberate defense-in-depth — which is correct for a genuinely out-of-project file, but a false negative on an IN-project file that merely arrives in short form is coverage loss, not defense.
Empirically scoped by volume (via cmd /c dir /x, which needs no elevation — fsutil 8dot3name query requires admin and returns "Error 5: Access is denied"):
C: generates short names — CAVEMA~1, CREDEN~1.JSO observed under C:\Users\KyleSexton\.claude.
D: does not — the short-name column is empty for every entry under a checkout there.
So the defect is live for repos on volumes that generate 8.3 names and dormant elsewhere. It is a per-volume property, not a per-plugin one — which is why it has stayed invisible: a contributor whose repos live on a non-generating volume cannot reproduce it.
Cost of leaving it: silent, telemetry-free lint gaps in two shipped formatter plugins for any consumer whose checkout sits on a short-name-generating volume.
Proposed work
- Decide the shared-lib disposition: teach
hook::physical_path to expand 8.3 short names on Windows hosts, or document the limitation at the function and have each consumer opt out as actionlint did. Prefer fixing the lib — three consumers have now met the same edge.
- Apply the outcome to
bash-format and markdown-format.
- Add a regression test that exercises a short-form in-project path, skipping cleanly on volumes that do not generate short names (mirroring the existing conditional-skip pattern in
plugins/markdown-format/hooks/markdown-format.test.sh, which already skips when its temp dir lands inside a git tree).
- Record the volume-scoped nature so a contributor on a non-generating volume does not read the test skip as a pass.
Acceptance criteria
References
Context
lib/hook-utils.sh'shook::physical_pathcanonicalizes a path withrealpath(falling back toreadlink -f) before theCLAUDE_PROJECT_DIRmembership comparison inhook::read_file_path. GNUrealpathunder Git Bash does not expand Windows 8.3 short names, so a short-formfile_pathfails the prefix match and the hook exits silently — no lint, no notice, no telemetry.actionlinthit this and fixed only itself. Its CHANGELOG 0.6.0 (#1133) records: "a short-formfile_path(the shape Claude Code's own scratchpad paths take) failed the prefix match and the hook exited silently — no lint, no notice, no telemetry... The hook now parses the path itself... the synced shared lib is untouched for consumers that need the guard."So the shared lib is still 8.3-blind, and
bash-formatandmarkdown-formatstill consume it. Both document the out-of-project skip as deliberate defense-in-depth — which is correct for a genuinely out-of-project file, but a false negative on an IN-project file that merely arrives in short form is coverage loss, not defense.Empirically scoped by volume (via
cmd /c dir /x, which needs no elevation —fsutil 8dot3name queryrequires admin and returns "Error 5: Access is denied"):C:generates short names —CAVEMA~1,CREDEN~1.JSOobserved underC:\Users\KyleSexton\.claude.D:does not — the short-name column is empty for every entry under a checkout there.So the defect is live for repos on volumes that generate 8.3 names and dormant elsewhere. It is a per-volume property, not a per-plugin one — which is why it has stayed invisible: a contributor whose repos live on a non-generating volume cannot reproduce it.
Cost of leaving it: silent, telemetry-free lint gaps in two shipped formatter plugins for any consumer whose checkout sits on a short-name-generating volume.
Proposed work
hook::physical_pathto expand 8.3 short names on Windows hosts, or document the limitation at the function and have each consumer opt out asactionlintdid. Prefer fixing the lib — three consumers have now met the same edge.bash-formatandmarkdown-format.plugins/markdown-format/hooks/markdown-format.test.sh, which already skips when its temp dir lands inside a git tree).Acceptance criteria
file_pathis linted rather than silently skipped, on a volume that generates 8.3 names.bash-formatandmarkdown-formateither consume the fixed lib or carry a documented, deliberate opt-out.References
lib/hook-utils.sh—hook::physical_path(~line 176) andhook::read_file_path(~line 193)plugins/actionlint/CHANGELOG.md0.6.0 (actionlint: membership guard silently skips lint on 8.3 short paths + cd-failure reads as clean + abs-path leak in telemetry #1133) — the same defect, fixed locally, shared lib deliberately untouchedplugins/bash-format/README.md:34-38,plugins/bash-format/skills/setup/SKILL.md:66-70— documented out-of-project skipplugins/markdown-format/hooks/markdown-format.test.sh:241-270— existing conditional-skip test patterndocs/conventions/hook-precision/— false-positive/false-negative discipline