diff --git a/.claude/hooks/worktree-guard.sh b/.claude/hooks/worktree-guard.sh index 7f32391..9077775 100755 --- a/.claude/hooks/worktree-guard.sh +++ b/.claude/hooks/worktree-guard.sh @@ -1,15 +1,16 @@ #!/usr/bin/env bash # Worktree isolation guard. This repo is worked by MULTIPLE concurrent sessions; if # they share the main checkout they clobber each other (branch switches, lost edits, -# commits landing on the wrong branch). Every session must work in its OWN git -# worktree OUTSIDE the main directory. +# commits on the wrong branch). Every session must work in its OWN git worktree +# OUTSIDE the main directory. # -# worktree-guard.sh block PreToolUse(Edit|Write|NotebookEdit) — DENY edits while -# the session is in the main checkout (emit deny JSON). +# worktree-guard.sh block PreToolUse(Edit|Write|NotebookEdit) — DENY edits to +# files INSIDE the main checkout while in it. Files +# outside the repo (memory, /tmp, scratchpad) are allowed. # worktree-guard.sh notice SessionStart — tell the agent to enter a worktree. # -# A session is "isolated" when it sits in a git worktree: there, `--git-dir` (…/.git/ -# worktrees/) differs from `--git-common-dir` (…/.git). Equal = the main checkout. +# Isolated = the session sits in a git worktree, where `--git-dir` (…/.git/worktrees/ +# ) differs from `--git-common-dir` (…/.git). Equal = the shared main checkout. set -u mode="${1:-notice}" @@ -29,6 +30,21 @@ Do NOT edit files in the main checkout." esc() { printf '%s' "$1" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))'; } if [ "$mode" = "block" ]; then + # Only guard files INSIDE the main checkout's working tree; allow edits to files + # outside the repo (auto-memory, /tmp, scratchpad, other repos). + fp=$(python3 -c 'import json,sys +try: + d=json.load(sys.stdin); print(d.get("tool_input",{}).get("file_path","") or "") +except Exception: print("")' 2>/dev/null) + case "$fp" in + /*) abs="$fp" ;; + "") abs="$root" ;; + *) abs="$root/$fp" ;; + esac + case "$abs" in + "$root"/*|"$root") : ;; # inside the repo → fall through to deny + *) exit 0 ;; # outside the repo → allow + esac printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":%s}}\n' "$(esc "$guidance")" else printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":%s}}\n' "$(esc "⚠ WORKTREE ISOLATION REQUIRED. $guidance")"