mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-23 04:26:54 +08:00
fix(hooks): avoid Claude Code v2.1.116 argv-dup bug in settings.local.json (#1524)
* 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>
This commit is contained in:
parent
92e0c7e9ff
commit
df9a478ea1
109
docs/fixes/HOOK-FIX-20260421-ADDENDUM.md
Normal file
109
docs/fixes/HOOK-FIX-20260421-ADDENDUM.md
Normal file
@ -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" "<path>"` — 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
|
||||||
144
docs/fixes/HOOK-FIX-20260421.md
Normal file
144
docs/fixes/HOOK-FIX-20260421.md
Normal file
@ -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`
|
||||||
60
docs/fixes/apply-hook-fix.sh
Normal file
60
docs/fixes/apply-hook-fix.sh
Normal file
@ -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"
|
||||||
Loading…
x
Reference in New Issue
Block a user