PreToolUse hooks: two Windows bugs — echo corrupts JSON, pre-approved permissions bypass hooks
Environment
- Claude Code (VS Code extension + CLI)
- Windows 11 Home, Git Bash
- Python 3.13
Bug 1: echo "$VAR" in bash hooks corrupts JSON backslashes on Windows
Description
PreToolUse hooks receive tool input as JSON via stdin. On Windows, file paths contain backslashes that are properly escaped in the JSON (\). However, when the hook script does:
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | python -c "import sys,json; ...")
echo interprets \ as \ before Python sees it, producing invalid JSON escapes like \U, \h, \p. Python's json.load() fails silently (stderr redirected), the variable becomes empty, and the hook falls through to exit 0 — silently allowing the tool call.
Expected behavior
Hook scripts should be able to reliably parse the JSON input on Windows.
Workaround
Skip JSON parsing entirely and grep the raw input:
INPUT=$(cat)
if echo "$INPUT" | grep -q '"file_path".*skills'; then
echo "blocked" >&2
exit 2
fi
Suggestion
Consider passing tool input via a temp file or environment variable instead of stdin, or document that echo is unsafe for re-piping JSON on Windows and recommend printf '%s' or heredoc.
---
Bug 2: Pre-approved Edit permissions bypass PreToolUse hooks entirely
Description
When ~/.claude/settings.json contains both:
- A PreToolUse hook on
Edit|Write:
"hooks": {
"PreToolUse": [{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "bash /path/to/hook.sh" }]
}]
}
- Pre-approved Edit permissions:
"permissions": {
"allow": ["Edit(~/.claude/skills/**)"]
}
The hook never fires for files matching the pre-approved pattern. The permission check short-circuits the hook execution.
Expected behavior
Hooks should fire regardless of permission settings. A user may want to allow edits without interactive prompting AND run a hook that adds context or warnings. These are orthogonal concerns — permissions answer "is this allowed?" while hooks answer "what should happen when this runs?"
Impact
This makes it impossible to build a "read skill-map before editing any skill" guardrail if skills are pre-approved for editing. The user must choose between:
- Pre-approve edits (convenient) but lose hook enforcement
- Remove pre-approval (annoying permission prompts every edit) to get hooks
Reproduction
- Add a PreToolUse hook on Edit|Write that writes to a log file
- Add
Edit(~/.claude/skills/**)to permissions.allow - Edit a file in
~/.claude/skills/ - Observe: no log file created, hook never ran
---
🤖 Generated with Claude Code
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗