Hook commands resolve interpreters against the host app's live environment — a desktop-app relaunch can flip bash to /bin/bash 3.2 and hard-wedge every PreToolUse(Bash) hook

Status Open
Maintainer reply None cached
Activity 0 comments · opened Jul 23, 2026

Summary

Hook commands (e.g. a PreToolUse hook registered as bash "$CLAUDE_PROJECT_DIR/scripts/hooks/dispatcher.sh") resolve their interpreter against the host app's live environment at spawn time. A desktop-app relaunch that resumes existing sessions can change that environment — in our case PATH lost its Homebrew prefix, so bash silently resolved to the system /bin/bash 3.2 instead of Homebrew bash 5.x. A hook script using bash-4+ syntax then failed to parse; bash's parse failure exits rc 2, which the PreToolUse contract treats as BLOCK — so every Bash tool call in five concurrent sessions was refused for the remainder of those sessions.

Why this was extremely hard to diagnose

  • The script was byte-identical to a known-good committed state the whole time; git diff showed nothing.
  • bash -n <script> from the user's terminal passed clean — the terminal resolves Homebrew bash 5.x, the wedged sessions' hook env resolved 3.2. Same bytes, different parser.
  • The in-session Read/Write tools showed correct, current file content throughout, and rewriting the file changed nothing — the error's line numbers never moved (unchanged content + unchanged 3.2 reader), which strongly mimics a stale-content/cache bug and sent us down that path first.
  • A freshly spawned subagent in the same session was equally wedged (it shares the session process/env).
  • The only recovery was abandoning each session and starting fresh from a terminal (restoring the expected PATH). There is no way to re-resolve or reload hooks mid-session (/hooks is read-only; #5513 tracks a reload command).

Timeline (one incident, five sessions)

Desktop-app restart resumed the sessions at ~T+0 (SessionStart:resume); the first hook parse failures appeared 30–70s later in each session; every Bash call thereafter failed identically until each session was restarted.

Asks

  1. Document the environment contract for hook commands: what environment do they inherit, and what can change across a desktop-app relaunch / session resume? (Today this is undocumented, and the failure mode presents as impossible: "the file is fine everywhere except inside the session".)
  2. Consider resolving hook commands via a login shell (or an explicitly pinned interpreter/PATH) so a host-app environment change can't silently swap the interpreter under long-lived sessions.
  3. A diagnostic breadcrumb would help enormously: when a hook exits 2, surfacing which interpreter binary ran it (path + version) would have collapsed days of debugging into minutes.
  4. Related, worth documenting alongside: $CLAUDE_PROJECT_DIR is substituted at hook-spawn from the project root as of session launch and never re-resolves after mid-session working-directory/worktree changes.

Environment

macOS (Darwin 25.x, arm64), Claude Code desktop app with resumed CLI sessions; system /bin/bash 3.2.57; Homebrew bash 5.x on the user PATH. Hooks are plain project-repo scripts (not plugins).

View original on GitHub ↗