[FEATURE] Long-lived worktree sessions silently run stale hook scripts — add a drift signal and an explicit hook script source

Status Open
Reported on v2.1.280
Maintainer reply None cached
Activity 0 comments · opened Sep 26, 2026

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:

  1. A PreToolUse guard was fixed on main to block a dangerous command shape (piping/redirecting the output of a merge command that another hook must parse).
  2. The fix was merged. New sessions picked it up.
  3. 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 against main by 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:

  1. 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 (/hooks status line or a session message): "N hook scripts in this worktree differ from <base> — guards may be outdated."
  2. 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 main by 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

  1. A team keeps security guards as project hooks.
  2. A guard bug is found and fixed on main.
  3. 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 — with hooks.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 sessions CLAUDE_PROJECT_DIR is the session worktree.

View original on GitHub ↗