#!/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