---
name: Hook Fix 20260421
description: Professional SynthOperator AI Agent Skill.
author: synthoperator
---

# 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`
