[FEATURE] Long-lived worktree sessions silently run stale hook scripts — add a drift signal and an explicit hook script source
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
In long-lived sessions that run inside a git worktree (for example the desktop app's per-session worktrees), project hooks are executed from that worktree's copy of .claude/hooks/ — the copy that existed when the worktree was created. Nothing refreshes it and nothing signals that it has drifted.
This matters most for guard hooks. A concrete case from our repo:
- A
PreToolUseguard was fixed onmainto block a dangerous command shape (piping/redirecting the output of a merge command that another hook must parse). - The fix was merged. New sessions picked it up.
- An already-running session whose worktree had been created much earlier (it was several hundred commits behind
main) kept executing the old hook. The dangerous command went through with no warning. We only found out by diffing the worktree's hook file againstmainby hand.
So a security fix to a hook silently does not protect sessions that are already running, and the session has no way to know it is running stale guards. The worktree cannot simply be synced either: it often has uncommitted work, so an automatic rebase/merge is not safe.
Hook registration is snapshotted at session start (documented), but the scripts themselves are re-read from disk on every call — the drift here is not caching, it is which checkout the scripts are read from.
Proposed Solution
Make the hook source explicit and make drift visible:
- Drift signal (smallest useful step): at
SessionStart(and optionally periodically), if the session runs in a git worktree, compare the tree's hook scripts referenced by settings against the same paths at the upstream/base branch tip. If they differ, surface one clear notice (/hooksstatus line or a session message): "N hook scripts in this worktree differ from <base> — guards may be outdated." - Optional source policy: a setting such as
hooks.scriptSource: "worktree" | "primary" | "<ref>"so a team can choose to execute guard hooks from the primary checkout (or a pinned ref) while the session's own tree stays untouched. Hook state/env (CLAUDE_PROJECT_DIR, cwd) should still point at the session tree; only the script location changes.
Alternative Solutions
- Syncing the session worktree with its base branch: unsafe when the tree has uncommitted work, which is the normal state mid-session.
- Our current workaround: a launcher script in front of every hook that rewrites the script path to a read-only checkout kept in sync with
mainby a scheduled job, plus an exclusion list for hooks that derive their state directory from__file__(those would otherwise write state into the read-only checkout). It works, but every project would need to reinvent it, and it has to also patch the hook dispatcher because child hooks are located via__file__, not via the launcher.
Related but different: #84723 asks for the opposite direction (run each worktree's own hooks so guard changes can be tested in that worktree). Both point at the same gap — there is no explicit policy for which checkout hook scripts come from, and no signal when the chosen copy drifts.
Priority
High - Significant impact on productivity
Feature Category
Configuration and settings
Use Case Example
- A team keeps security guards as project hooks.
- A guard bug is found and fixed on
main. - Developers with sessions already open in older worktrees are told at their next
SessionStart/next hook status check that their guards are outdated (drift signal), or — withhooks.scriptSource: "primary"— they are protected immediately without touching their in-progress worktree.
Additional Context
- Claude Code 2.1.280, macOS, sessions created by the desktop app in per-session git worktrees.
- Hooks registered as
${CLAUDE_PROJECT_DIR}/.claude/hooks/<launcher> ${CLAUDE_PROJECT_DIR}/.claude/hooks/<dispatcher>.py; in these sessionsCLAUDE_PROJECT_DIRis the session worktree.