Skip to content

hooks: adopt peer-session worktree guard (enforce-session-worktree.sh) as a managed hook #153

Description

@millsmillsymills

Context

Two separately-launched peer `claude` sessions sharing one repo's main checkout collide: branch flips, stray untracked files, half-committed state. This actually happened (an iotcred session + an identity/ATO session in the same checkout, leaving a stray `scripts/session-probe.sh`).

The existing `hooks/force-worktree-isolation.sh` (PreToolUse `Agent|Task`) only isolates subagents into worktrees — it does nothing for peer sessions. A complementary guard for peer sessions was written and is already live as an UNMANAGED real file at `/.claude/hooks/enforce-session-worktree.sh`, wired in `/.claude/settings.json` (SessionStart + SessionEnd), and verified end-to-end. This issue is to bring it under version control in this repo so it's managed/symlinked/documented like every other hook.

Change

  1. Move `enforce-session-worktree.sh` into `hooks/` in this repo (so `install.sh`/`doctor.sh` symlink it into `/.claude/hooks/` like the others). Remove the unmanaged real file at `/.claude/hooks/enforce-session-worktree.sh` once the symlink exists.
  2. Wire it in the settings template used by installs:
    • `SessionStart` -> `run-hook.sh enforce-session-worktree.sh start`
    • `SessionEnd` -> `run-hook.sh enforce-session-worktree.sh end`
  3. Document it in `docs/HOOKS.md` alongside `force-worktree-isolation.sh` — the two together enforce "all parallel work uses worktrees" (subagents + peer sessions).
  4. Design decision to confirm: keep the self-contained dual-mode (`start`/`end`) hook, or fold the SessionEnd release into `cleanup-session-markers.sh` (which already runs on SessionEnd). Dual-mode was chosen to keep the hook self-contained; either is fine.

Design (as implemented + tested)

  • First session in a repo's MAIN checkout claims an owner marker `~/.claude/state/worktree-owner-` (` `). A later session starting in the SAME main checkout while the marker is fresh gets a SessionStart `additionalContext` advisory to create its own git worktree before any Edit/Write.
  • Advisory, never blocks — a hard block keyed on liveness would wrongly lock out a legitimate solo session after a crash; this errs toward worktrees instead.
  • Skips linked worktrees (git-dir under `.git/worktrees/`) and non-repos.
  • SessionEnd `end` mode releases the session's marker, so sequential (non-overlapping) sessions are never nagged. Backstops: 6h staleness in-script + a `find -mtime +1` sweep for crashed owners.
  • Verified: primary claims; concurrent session gets the advisory JSON; same-sid reclaim is silent; non-repo ignored; end releases; post-release sequential session claims cleanly (no nag).

Acceptance

  • `hooks/enforce-session-worktree.sh` tracked in this repo; `~/.claude/hooks/` entry is a symlink to it (not a real file).
  • SessionStart/SessionEnd wiring present in the install's settings template.
  • `docs/HOOKS.md` documents both worktree hooks.
  • `shellcheck`/`shfmt` clean.

The current live script is at `~/.claude/hooks/enforce-session-worktree.sh` on this machine — move that exact file.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions