Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ JIT memory management framework for Claude Code -- persistent context across ses

## Status
- **Phase:** Active Development
- **Version:** v0.4.0
- **Version:** v0.4.1
- **Repo:** https://github.com/dstolts/jitneuro

## Tech Stack
Expand Down
85 changes: 85 additions & 0 deletions RELEASE-NOTES-v0.4.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# JitNeuro v0.4.1 Release Notes

**Release Date:** May 20, 2026
**Stability:** Stable
**Breaking Changes:** None
**Action Required:** YES -- all users must re-run the installer (see "How to update" below)

---

## Summary

v0.4.1 is a fix release. The installer (`install.sh` and `install.ps1`) was registering Claude Code hook commands in a form that fails at runtime on every recent Claude Code version. As a result, hooks installed by v0.4.0 (and earlier) produce an error on every tool call instead of running.

This release corrects the installer. Because the broken hook commands are already written into your `settings.local.json`, updating the repo is not enough on its own -- you must re-run the installer so the corrected commands replace the broken ones.

**Who should update:** Everyone who installed JitNeuro v0.4.0 or earlier.

---

## The bug

JitNeuro's installer wrote each hook `command` with a shell prefix:

- `install.sh` emitted: `bash "<path>/hook.sh"`
- `install.ps1` emitted: `"<git-bash-path>" "<path>/hook.sh"`

Claude Code runs each hook `command` through its OWN shell. A shell prefix makes that shell try to execute bash as bash's own script argument, so every hook fails at runtime with:

```
bash.exe: bash.exe: cannot execute binary file
```

Every hook event is affected -- `SessionStart`, `PreToolUse`, `PostToolUse`, `SessionEnd`, `PreCompact`. The visible result is an error banner on session start and on every tool call, and none of the hooks (heartbeat, branch protection, session write-id, auto-save, etc.) actually run.

## The fix

The installer now writes each hook `command` as a bare script path -- no shell prefix:

```
"command": "<path>/hook.sh"
```

Claude Code's own shell executes the script directly. Both `install.sh` and `install.ps1` are corrected, and both now carry a "HOW TO WRITE A HOOK COMMAND" instruction block above the hook configuration so the bug cannot silently reappear in future edits.

## How to recognize you are affected

If, since installing JitNeuro, you have seen any of these in Claude Code:

- `SessionStart:startup hook ... cannot execute binary file`
- Hook errors on every tool call
- `bash.exe: bash.exe: cannot execute binary file`

then your hooks are running the broken commands and you need this update.

---

## How to update

1. Pull the latest JitNeuro (v0.4.1 or later).
2. Re-run the installer for your platform, in the same mode you originally used:
- macOS / Linux / Git Bash: `bash install.sh`
- Windows PowerShell: `pwsh -File install.ps1`
3. The installer rewrites the hook entries in `settings.local.json` with the corrected commands.
4. Restart Claude Code. Hook configuration is loaded at session start, so the running session will not pick up the fix until you start a new one.

After restarting, session start and tool calls should be free of the `cannot execute binary file` error.

### If you do not want to re-run the installer

You can fix it by hand: open your `settings.local.json`, find every `hooks.*` entry, and change each `command` from `bash "<path>"` (or `"<bash-path>" "<path>"`) to just `<path>` -- the bare script path, nothing else. Then restart Claude Code.

---

## What changed in this release

- `install.sh` -- hook commands written as bare script paths; instruction block added.
- `install.ps1` -- hook commands written as bare script paths; instruction block added.
- No changes to hook scripts, hook events, matchers, or timeouts.
- No changes to commands, bundles, engrams, or any other JitNeuro feature.

## Notes

- This is a fix-only release. There are no new features and no breaking changes.
- The hook scripts themselves were always correct -- only the installer's registration of them was wrong.
- `pre-compact-save.sh` exiting with a blocking status before compaction is intended behavior (it asks you to `/save`), not part of this bug.
34 changes: 25 additions & 9 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -280,70 +280,86 @@ Write-Host "Configuring hooks..." -ForegroundColor Green
$SettingsFile = Join-Path $Target "settings.local.json"
$HooksPathFwd = ($hooksDir -replace '\\', '/')

# ============================================================================
# HOW TO WRITE A HOOK COMMAND -- read before editing the entries below
# ----------------------------------------------------------------------------
# Claude Code runs each hook `command` through its OWN shell. The `command`
# value MUST be a bare script path and nothing else, for example:
# command = "$HooksPathFwd/heartbeat.sh"
#
# NEVER prefix it with bash, bash.exe, or any shell binary. A prefix makes the
# shell try to run bash as bash's own script argument, so EVERY hook then
# fails at runtime with:
# bash.exe: bash.exe: cannot execute binary file
#
# When adding or changing a hook below, look at the other hook entries in this
# same block and match their format exactly -- each command is just a path.
# Full rule: jit-knowledge/rules/claude-code-hook-deployment.md
# ============================================================================
# Build hooks config object
$hooksConfig = @{
hooks = @{
PreCompact = @(
@{
matcher = ""
hooks = @(
@{ type = "command"; command = "$BashPathFwd `"$HooksPathFwd/pre-compact-save.sh`""; timeout = 10 }
@{ type = "command"; command = "$HooksPathFwd/pre-compact-save.sh"; timeout = 10 }
)
}
)
SessionStart = @(
@{
matcher = ""
hooks = @(
@{ type = "command"; command = "$BashPathFwd `"$HooksPathFwd/session-start-write-id.sh`""; timeout = 10 }
@{ type = "command"; command = "$HooksPathFwd/session-start-write-id.sh"; timeout = 10 }
)
}
@{
matcher = ""
hooks = @(
@{ type = "command"; command = "$BashPathFwd `"$HooksPathFwd/session-start-post-clear.sh`""; timeout = 10 }
@{ type = "command"; command = "$HooksPathFwd/session-start-post-clear.sh"; timeout = 10 }
)
}
@{
matcher = "compact"
hooks = @(
@{ type = "command"; command = "$BashPathFwd `"$HooksPathFwd/session-start-recovery.sh`""; timeout = 10 }
@{ type = "command"; command = "$HooksPathFwd/session-start-recovery.sh"; timeout = 10 }
)
}
)
PreToolUse = @(
@{
matcher = "Bash"
hooks = @(
@{ type = "command"; command = "$BashPathFwd `"$HooksPathFwd/branch-protection.sh`""; timeout = 10 }
@{ type = "command"; command = "$HooksPathFwd/branch-protection.sh"; timeout = 10 }
)
}
@{
matcher = "Agent"
hooks = @(
@{ type = "command"; command = "$BashPathFwd `"$HooksPathFwd/pre-agent-register.sh`""; timeout = 5 }
@{ type = "command"; command = "$HooksPathFwd/pre-agent-register.sh"; timeout = 5 }
)
}
)
PostToolUse = @(
@{
matcher = ""
hooks = @(
@{ type = "command"; command = "$BashPathFwd `"$HooksPathFwd/heartbeat.sh`""; timeout = 5 }
@{ type = "command"; command = "$HooksPathFwd/heartbeat.sh"; timeout = 5 }
)
}
@{
matcher = "Agent"
hooks = @(
@{ type = "command"; command = "$BashPathFwd `"$HooksPathFwd/post-agent-complete.sh`""; timeout = 5 }
@{ type = "command"; command = "$HooksPathFwd/post-agent-complete.sh"; timeout = 5 }
)
}
)
SessionEnd = @(
@{
matcher = ""
hooks = @(
@{ type = "command"; command = "$BashPathFwd `"$HooksPathFwd/session-end-autosave.sh`""; timeout = 10 }
@{ type = "command"; command = "$HooksPathFwd/session-end-autosave.sh"; timeout = 10 }
)
}
)
Expand Down
34 changes: 25 additions & 9 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -253,25 +253,41 @@ HOOKS_PATH="$TARGET/hooks"
# Use forward slashes for all paths (Claude Code expects this)
HOOKS_PATH_FWD=$(echo "$HOOKS_PATH" | sed 's|\\|/|g')

# ============================================================================
# HOW TO WRITE A HOOK COMMAND -- read before editing the entries below
# ----------------------------------------------------------------------------
# Claude Code runs each hook `command` through its OWN shell. The `command`
# value MUST be a bare script path and nothing else, for example:
# "command": "${HOOKS_PATH_FWD}/heartbeat.sh"
#
# NEVER prefix it with `bash`, `bash.exe`, or any shell binary. A prefix makes
# the shell try to run bash as bash's own script argument, so EVERY hook then
# fails at runtime with:
# bash.exe: bash.exe: cannot execute binary file
#
# When adding or changing a hook below, look at the other hook entries in this
# same block and match their format exactly -- each `command` is just a path.
# Full rule: jit-knowledge/rules/claude-code-hook-deployment.md
# ============================================================================
build_hooks_json() {
cat <<HOOKJSON
{
"hooks": {
"PreCompact": [{ "matcher": "", "hooks": [{ "type": "command", "command": "bash \"${HOOKS_PATH_FWD}/pre-compact-save.sh\"", "timeout": 10 }] }],
"PreCompact": [{ "matcher": "", "hooks": [{ "type": "command", "command": "${HOOKS_PATH_FWD}/pre-compact-save.sh", "timeout": 10 }] }],
"SessionStart": [
{ "matcher": "", "hooks": [{ "type": "command", "command": "bash \"${HOOKS_PATH_FWD}/session-start-write-id.sh\"", "timeout": 10 }] },
{ "matcher": "", "hooks": [{ "type": "command", "command": "bash \"${HOOKS_PATH_FWD}/session-start-post-clear.sh\"", "timeout": 10 }] },
{ "matcher": "compact", "hooks": [{ "type": "command", "command": "bash \"${HOOKS_PATH_FWD}/session-start-recovery.sh\"", "timeout": 10 }] }
{ "matcher": "", "hooks": [{ "type": "command", "command": "${HOOKS_PATH_FWD}/session-start-write-id.sh", "timeout": 10 }] },
{ "matcher": "", "hooks": [{ "type": "command", "command": "${HOOKS_PATH_FWD}/session-start-post-clear.sh", "timeout": 10 }] },
{ "matcher": "compact", "hooks": [{ "type": "command", "command": "${HOOKS_PATH_FWD}/session-start-recovery.sh", "timeout": 10 }] }
],
"PreToolUse": [
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "bash \"${HOOKS_PATH_FWD}/branch-protection.sh\"", "timeout": 10 }] },
{ "matcher": "Agent", "hooks": [{ "type": "command", "command": "bash \"${HOOKS_PATH_FWD}/pre-agent-register.sh\"", "timeout": 5 }] }
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "${HOOKS_PATH_FWD}/branch-protection.sh", "timeout": 10 }] },
{ "matcher": "Agent", "hooks": [{ "type": "command", "command": "${HOOKS_PATH_FWD}/pre-agent-register.sh", "timeout": 5 }] }
],
"PostToolUse": [
{ "matcher": "", "hooks": [{ "type": "command", "command": "bash \"${HOOKS_PATH_FWD}/heartbeat.sh\"", "timeout": 5 }] },
{ "matcher": "Agent", "hooks": [{ "type": "command", "command": "bash \"${HOOKS_PATH_FWD}/post-agent-complete.sh\"", "timeout": 5 }] }
{ "matcher": "", "hooks": [{ "type": "command", "command": "${HOOKS_PATH_FWD}/heartbeat.sh", "timeout": 5 }] },
{ "matcher": "Agent", "hooks": [{ "type": "command", "command": "${HOOKS_PATH_FWD}/post-agent-complete.sh", "timeout": 5 }] }
],
"SessionEnd": [{ "matcher": "", "hooks": [{ "type": "command", "command": "bash \"${HOOKS_PATH_FWD}/session-end-autosave.sh\"", "timeout": 10 }] }]
"SessionEnd": [{ "matcher": "", "hooks": [{ "type": "command", "command": "${HOOKS_PATH_FWD}/session-end-autosave.sh", "timeout": 10 }] }]
}
}
HOOKJSON
Expand Down
2 changes: 1 addition & 1 deletion templates/jitneuro.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.4.0",
"version": "0.4.1",
"hooks": {
"preCompactBehavior": "block",
"autosave": true,
Expand Down