From df9a478ea1dba0d7bae20862784e31a29f0cbf6b Mon Sep 17 00:00:00 2001 From: suusuu0927 <269936893+suusuu0927@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:35:33 +0900 Subject: [PATCH] fix(hooks): avoid Claude Code v2.1.116 argv-dup bug in settings.local.json (#1524) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 ` 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 --- docs/fixes/HOOK-FIX-20260421-ADDENDUM.md | 109 +++++++++++++++++ docs/fixes/HOOK-FIX-20260421.md | 144 +++++++++++++++++++++++ docs/fixes/apply-hook-fix.sh | 60 ++++++++++ 3 files changed, 313 insertions(+) create mode 100644 docs/fixes/HOOK-FIX-20260421-ADDENDUM.md create mode 100644 docs/fixes/HOOK-FIX-20260421.md create mode 100644 docs/fixes/apply-hook-fix.sh diff --git a/docs/fixes/HOOK-FIX-20260421-ADDENDUM.md b/docs/fixes/HOOK-FIX-20260421-ADDENDUM.md new file mode 100644 index 00000000..6dc996bd --- /dev/null +++ b/docs/fixes/HOOK-FIX-20260421-ADDENDUM.md @@ -0,0 +1,109 @@ +# HOOK-FIX-20260421 Addendum — v2.1.116 argv 重複バグ + +朝セッションで commit 527c18b として修正済み。夜セッションで追加検証と、 +朝fix でカバーしきれない Claude Code 固有のバグを特定したので補遺を記録する。 + +## 朝fixの形式 + +```json +"command": "C:/Users/sugig/.claude/skills/continuous-learning/hooks/observe-wrapper.sh pre" +``` + +`.sh` ファイルを直接 command にする形式。Git Bash が shebang 経由で実行する前提。 + +## 夜 追加検証で判明したこと + +Node.js の `child_process.spawn` で `.sh` ファイルを直接実行すると Windows では +**EFTYPE** で失敗する: + +```js +spawn('C:/Users/sugig/.claude/skills/continuous-learning/hooks/observe-wrapper.sh', + ['post'], {stdio:['pipe','pipe','pipe']}); +// → Error: spawn EFTYPE (errno -4028) +``` + +`shell:true` を付ければ cmd.exe 経由で実行できるが、Claude Code 側の実装 +依存のリスクが残る。 + +## 夜 適用した追加 fix + +第1トークンを `bash`(PATH 解決)に変えた明示的な呼び出しに更新: + +```json +{ + "hooks": { + "PreToolUse": [{ + "matcher": "*", + "hooks": [{ + "type": "command", + "command": "bash \"C:/Users/sugig/.claude/skills/continuous-learning/hooks/observe-wrapper.sh\" pre" + }] + }], + "PostToolUse": [{ + "matcher": "*", + "hooks": [{ + "type": "command", + "command": "bash \"C:/Users/sugig/.claude/skills/continuous-learning/hooks/observe-wrapper.sh\" post" + }] + }] + } +} +``` + +この形式は `~/.claude/hooks/hooks.json` 内の ECC 正規 observer 登録と +同じパターンで、現実にエラーなく動作している実績あり。 + +### Node spawn 検証 + +```js +spawn('bash "C:/Users/sugig/.claude/skills/continuous-learning/hooks/observe-wrapper.sh" post', + [], {shell:true}); +// exit=0 → observations.jsonl に正常追記 +``` + +## Claude Code v2.1.116 の argv 重複バグ(詳細) + +朝fix docの「Defect 2」として `bash.exe: bash.exe: cannot execute binary file` を +記録しているが、その根本メカニズムが特定できたので記す。 + +### 再現 + +```bash +"C:\Program Files\Git\bin\bash.exe" "C:\Program Files\Git\bin\bash.exe" +# stderr: "C:\Program Files\Git\bin\bash.exe: C:\Program Files\Git\bin\bash.exe: cannot execute binary file" +# exit: 126 +``` + +bash は argv[1] を script とみなし読み込もうとする。argv[1] が bash.exe 自身なら +ELF/PE バイナリ検出で失敗 → exit 126。エラー文言は完全一致。 + +### Claude Code 側の挙動 + +hook command が `"C:\Program Files\Git\bin\bash.exe" "C:\Users\...\wrapper.sh"` +のとき、v2.1.116 は**第1トークン(= bash.exe フルパス)を argv[0] と argv[1] の +両方に渡す**と推定される。結果 bash は argv[1] = bash.exe を script として +読み込もうとして 126 で落ちる。 + +### 回避策 + +第1トークンを bash.exe のフルパス+スペース付きパスにしないこと: +1. ✅ `bash` (PATH 解決の単一トークン)— 夜fix / hooks.json パターン +2. ✅ `.sh` 直接パス(Claude Code の .sh ハンドリングに依存)— 朝fix +3. ❌ `"C:\Program Files\Git\bin\bash.exe" ""` — 1トークン目が quoted で空白込み + +## 結論 + +朝fix(直接 .sh 指定)と夜fix(明示的 bash prefix)のどちらも argv 重複バグを +踏まないが、**夜fixの方が Claude Code の実装依存が少ない**ため推奨。 + +ただし朝fix commit 527c18b は既に docs/fixes/ に入っているため、この Addendum を +追記することで両論併記とする。次回 CLI 再起動時に夜fix の方が実運用に残る。 + +## 関連 + +- 朝 fix commit: 527c18b +- 朝 fix doc: docs/fixes/HOOK-FIX-20260421.md +- 朝 apply script: docs/fixes/apply-hook-fix.sh +- 夜 fix 記録(ローカル): C:\Users\sugig\Documents\Claude\Projects\ECC作成\hook-fix-report-20260421.md +- 夜 fix 適用ファイル: C:\Users\sugig\.claude\settings.local.json +- 夜 backup: C:\Users\sugig\.claude\settings.local.json.bak-hook-fix-20260421 diff --git a/docs/fixes/HOOK-FIX-20260421.md b/docs/fixes/HOOK-FIX-20260421.md new file mode 100644 index 00000000..cf968fd8 --- /dev/null +++ b/docs/fixes/HOOK-FIX-20260421.md @@ -0,0 +1,144 @@ +# 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: + +```json +"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: + +```json +"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: + +```json +{ + "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: + +```bash +# 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` — rewritten +- `C:\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` — overwrites `settings.local.json` with + UTF-8-BOM + CRLF and re-introduces the double-wrapped `bash.exe` command. + Should be replaced with a patcher that emits UTF-8 (no BOM), LF, and a + direct `.sh` path. + +## Branch + +`claude/hook-fix-20260421` diff --git a/docs/fixes/apply-hook-fix.sh b/docs/fixes/apply-hook-fix.sh new file mode 100644 index 00000000..04dda4a7 --- /dev/null +++ b/docs/fixes/apply-hook-fix.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Apply ECC hook fix to ~/.claude/settings.local.json. +# +# - Creates a timestamped backup next to the original. +# - Rewrites the file as UTF-8 (no BOM), LF line endings. +# - Routes hook commands directly at observe-wrapper.sh with a "pre"/"post" arg. +# +# Related fix doc: docs/fixes/HOOK-FIX-20260421.md + +set -euo pipefail + +TARGET="${1:-$HOME/.claude/settings.local.json}" +WRAPPER="${ECC_OBSERVE_WRAPPER:-$HOME/.claude/skills/continuous-learning/hooks/observe-wrapper.sh}" + +if [ ! -f "$WRAPPER" ]; then + echo "[hook-fix] wrapper not found: $WRAPPER" >&2 + exit 1 +fi + +mkdir -p "$(dirname "$TARGET")" + +if [ -f "$TARGET" ]; then + ts="$(date +%Y%m%d-%H%M%S)" + cp "$TARGET" "$TARGET.bak-hookfix-$ts" + echo "[hook-fix] backup: $TARGET.bak-hookfix-$ts" +fi + +# Convert wrapper path to forward-slash form for JSON. +wrapper_fwd="$(printf '%s' "$WRAPPER" | tr '\\\\' '/')" + +# Write the new config as UTF-8 (no BOM), LF line endings. +printf '%s\n' '{ + "hooks": { + "PreToolUse": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "'"$wrapper_fwd"' pre" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "'"$wrapper_fwd"' post" + } + ] + } + ] + } +}' > "$TARGET" + +echo "[hook-fix] wrote: $TARGET" +echo "[hook-fix] restart the claude CLI for changes to take effect"