* fix: resolve Claude Code Bash hook "cannot execute binary file" on Windows Root cause in ~/.claude/settings.local.json (user-global): 1. UTF-8 BOM + CRLF line endings left by patch_settings_cl_v2_simple.ps1 2. Double-wrapped command "\"bash.exe\" \"wrapper.sh\"" broke Windows argument splitting on the space in "Program Files", making bash.exe try to execute itself as a script. Fix: - Rewrite settings.local.json as UTF-8 (no BOM), LF, with the hook command pointing directly at observe-wrapper.sh and passing "pre"/"post" as a positional arg so HOOK_PHASE is populated correctly in observe.sh. Docs: - docs/fixes/HOOK-FIX-20260421.md — full root-cause analysis. - docs/fixes/apply-hook-fix.sh — idempotent applier script. * docs: addendum for HOOK-FIX-20260421 (v2.1.116 argv duplication detail) - Documents Claude Code v2.1.116 argv duplication bug as the underlying cause of the bash.exe:bash.exe:cannot execute binary file error - Records night-session fix variant using explicit `bash <path>` prefix (matches hooks.json observer pattern, avoids EFTYPE on Node spawn) - Keeps morning commit 527c18b intact; both variants are now documented --------- Co-authored-by: suusuu0927 <sugi.go.go.gm@gmail.com>
4.5 KiB
ECC Hook Fix — 2026-04-21
Summary
Claude Code CLI v2.1.116 on Windows was failing all Bash tool hook invocations with:
PreToolUse:Bash hook error
Failed with non-blocking status code:
C:\Program Files\Git\bin\bash.exe: C:\Program Files\Git\bin\bash.exe:
cannot execute binary file
PostToolUse:Bash hook error (同上)
Result: observations.jsonl stopped updating after 2026-04-20T23:03:38Z
(last entry was a parse_error from an earlier BOM-on-stdin issue).
Root Cause
C:\Users\sugig\.claude\settings.local.json had two defects:
Defect 1 — UTF-8 BOM + CRLF line endings
The file started with EF BB BF (UTF-8 BOM) and used CRLF line terminators.
This is the PowerShell ConvertTo-Json | Out-File default behavior, and it is
what patch_settings_cl_v2_simple.ps1 leaves behind when it rewrites the file.
00000000: efbb bf7b 0d0a 2020 2020 2268 6f6f 6b73 ...{.. "hooks
Defect 2 — Double-wrapped bash.exe invocation
The command string explicitly re-invoked bash.exe:
"command": "\"C:\\Program Files\\Git\\bin\\bash.exe\" \"C:\\Users\\sugig\\.claude\\skills\\continuous-learning\\hooks\\observe-wrapper.sh\""
When Claude Code spawns this on Windows, argument splitting does not preserve
the quoted "C:\Program Files\..." token correctly. The embedded space in
Program Files splits argv[0], and bash.exe ends up being passed to
itself as a script file, producing:
bash.exe: bash.exe: cannot execute binary file
Prior working shape (for reference)
Before patch_settings_cl_v2_simple.ps1 ran, the command was simply:
"command": "C:\\Users\\sugig\\.claude\\skills\\continuous-learning\\hooks\\observe.sh"
Claude Code on Windows detects .sh and invokes it via Git Bash itself — no
manual bash.exe wrapping needed.
Fix
C:\Users\sugig\.claude\settings.local.json rewritten as UTF-8 (no BOM), LF
line endings, with the command pointing directly at the wrapper .sh and
passing the hook phase as a plain argument:
{
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "C:/Users/sugig/.claude/skills/continuous-learning/hooks/observe-wrapper.sh pre"
}
]
}
],
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "C:/Users/sugig/.claude/skills/continuous-learning/hooks/observe-wrapper.sh post"
}
]
}
]
}
}
Side benefit: the pre / post argument is now routed to observe.sh's
HOOK_PHASE variable so events are correctly logged as tool_start vs
tool_complete (previously everything was recorded as tool_complete).
Verification
Direct invocation of the new command format, emulating both hook phases:
# PostToolUse path
echo '{"tool_name":"Bash","tool_input":{"command":"pwd"},"session_id":"post-fix-verify-001","cwd":"...","hook_event_name":"PostToolUse"}' \
| "C:/Users/sugig/.claude/skills/continuous-learning/hooks/observe-wrapper.sh" post
# exit=0
# PreToolUse path
echo '{"tool_name":"Bash","tool_input":{"command":"ls"},"session_id":"post-fix-verify-pre-001","cwd":"...","hook_event_name":"PreToolUse"}' \
| "C:/Users/sugig/.claude/skills/continuous-learning/hooks/observe-wrapper.sh" pre
# exit=0
observations.jsonl gained:
{"timestamp":"2026-04-21T05:57:54Z","event":"tool_complete","tool":"Bash","session":"post-fix-verify-001",...}
{"timestamp":"2026-04-21T05:57:55Z","event":"tool_start","tool":"Bash","session":"post-fix-verify-pre-001","input":"{\"command\":\"ls\"}",...}
Both phases now produce correctly typed events.
Note on live CLI verification: settings changes take effect on the next
claude CLI session launch. Restart the CLI and run a Bash tool call to
confirm new rows appear in observations.jsonl from the actual CLI session.
Files Touched
C:\Users\sugig\.claude\settings.local.json— rewrittenC:\Users\sugig\.claude\settings.local.json.bak-hookfix-20260421-145718— pre-fix backup
Known Upstream Bugs (not fixed here)
install_hook_wrapper.ps1— halts at step [3/4], never reaches [4/4].patch_settings_cl_v2_simple.ps1— overwritessettings.local.jsonwith UTF-8-BOM + CRLF and re-introduces the double-wrappedbash.execommand. Should be replaced with a patcher that emits UTF-8 (no BOM), LF, and a direct.shpath.
Branch
claude/hook-fix-20260421