feat(hooks): block backslashed Windows-path redirects in bash

Adds a PreToolUse(Bash) hook (block-backslash-winpath.sh) that rejects commands
redirecting/writing to a backslashed Windows drive path (e.g. > D:\claudetools\
.claude\current-mode). On Git Bash those strip the backslashes and PUA-substitute
':' (U+F03A), creating garbled junk files that have repeatedly polluted the repo.
The hook quote-strips the command first, so the pattern appearing inside strings
or commit messages does not false-trigger; Windows-tool args (icacls, pwsh -File)
and forward-slash/relative paths pass. Wired into settings.json so every machine
picks it up on /sync. Pairs with the sync.sh staging guard.

Also: CLAUDE.md note on the Windows mode-write path; record jq install on GURU-KALI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 10:38:14 -07:00
parent a3c7064606
commit 6d065cf3ee
4 changed files with 53 additions and 1 deletions

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# PreToolUse(Bash) hook: block bash commands that REDIRECT/WRITE to a backslashed
# Windows drive path (e.g. `echo x > D:\claudetools\.claude\current-mode`).
#
# Why: under Git Bash / MSYS, a backslash is an escape char. `> D:\foo\bar`
# strips the backslashes and substitutes the illegal ':' with the Unicode
# Private-Use char U+F03A, creating a garbled junk file in the CWD instead of
# writing the intended path. These junk files then pollute the repo. Use a
# relative path or forward slashes (/d/claudetools/... or D:/claudetools/...).
#
# Only flags WRITE targets (> / >> / tee). Windows-tool arguments like
# `icacls "D:\Homes"` or `pwsh -File C:\x.ps1` are NOT redirects, so they pass.
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null)
# Strip quoted substrings first, so the pattern appearing INSIDE a string or a
# commit message (e.g. git commit -m "... > D:\\path ...") does not false-trigger.
# A real bareword redirect target lives OUTSIDE quotes. (A redirect to a *quoted*
# path like `> "D:\x"` is rarer and is still caught by sync.sh's staging guard.)
bare=$(printf '%s' "$cmd" | sed -E "s/'[^']*'//g; s/\"[^\"]*\"//g")
# Block when, after quote-stripping, a redirect/tee writes to a bareword X:\ path.
if printf '%s' "$bare" | grep -qiP '(>>?|tee)\s*[A-Za-z]:\\'; then
echo "BLOCKED: do not redirect/write to a backslashed Windows path in bash."
echo ""
echo "Git Bash strips the backslashes and PUA-substitutes ':', creating a"
echo "garbled junk file instead of writing the path you intended."
echo ""
echo "Use one of these instead:"
echo " - relative path: echo dev > .claude/current-mode"
echo " - MSYS forward-slash: echo dev > /d/claudetools/.claude/current-mode"
echo " - drive forward-slash: echo dev > D:/claudetools/.claude/current-mode"
exit 2
fi
exit 0