From 17856cac3256303095d9d9e63c2fc2ed89facdcf Mon Sep 17 00:00:00 2001 From: BenStullsBets Date: Tue, 30 Jun 2026 10:15:57 -0700 Subject: [PATCH] chore(dev): enforce isolated worktrees outside main for every session Multiple sessions sharing this one checkout clobbered each other (branch switches, edits landing on the wrong branch). Enforce per-session isolation: - worktree.bgIsolation=worktree + baseRef=fresh (native bg-session isolation) - PreToolUse(Edit|Write|NotebookEdit) hook DENIES edits while in the main checkout - SessionStart hook directs each session to EnterWorktree (fresh worktree, off main) .claude/hooks/worktree-guard.sh detects main-vs-worktree via git-dir != git-common-dir. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/hooks/worktree-guard.sh | 35 +++++++++++++++++++++++++++++++++ .claude/settings.json | 30 ++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100755 .claude/hooks/worktree-guard.sh create mode 100644 .claude/settings.json diff --git a/.claude/hooks/worktree-guard.sh b/.claude/hooks/worktree-guard.sh new file mode 100755 index 0000000..7f32391 --- /dev/null +++ b/.claude/hooks/worktree-guard.sh @@ -0,0 +1,35 @@ +#!/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. +# +# worktree-guard.sh block PreToolUse(Edit|Write|NotebookEdit) — DENY edits while +# the session is in the main checkout (emit deny JSON). +# 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. +set -u +mode="${1:-notice}" + +gd=$(git rev-parse --absolute-git-dir 2>/dev/null) || exit 0 # not a repo → nothing to guard +gc=$(cd "$(git rev-parse --git-common-dir 2>/dev/null)" 2>/dev/null && pwd -P) || exit 0 +[ "$gd" = "$gc" ] || exit 0 # already in a worktree → allow + +root=$(git rev-parse --show-toplevel 2>/dev/null) +repo=$(basename "${root:-repo}") +guidance="You are in the SHARED main checkout ($root). Multiple sessions use it \ +concurrently and WILL clobber each other. Work in an isolated worktree OUTSIDE the \ +main directory: use the EnterWorktree tool (preferred — it relocates this session to \ +a fresh worktree branched from origin/main), or run \ +\`git worktree add -b ../${repo}-worktrees/ origin/main\` and cd there. \ +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 + printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":%s}}\n' "$(esc "$guidance")" +else + printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":%s}}\n' "$(esc "⚠ WORKTREE ISOLATION REQUIRED. $guidance")" +fi diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..68a811e --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "worktree": { + "bgIsolation": "worktree", + "baseRef": "fresh" + }, + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/worktree-guard.sh\" notice" + } + ] + } + ], + "PreToolUse": [ + { + "matcher": "Edit|Write|NotebookEdit", + "hooks": [ + { + "type": "command", + "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/worktree-guard.sh\" block" + } + ] + } + ] + } +}