Compare commits

...

2 Commits

Author SHA1 Message Date
BenStullsBets d711db6a2c fix(guard): scope the edit-block to files INSIDE the repo
The PreToolUse guard blocked ALL edits while in the main checkout — including files
outside the repo (auto-memory, /tmp, scratchpad). Now it only denies edits to files
under the main checkout's working tree; external files are allowed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:18:39 -07:00
BenStullsBets 17856cac32 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) <noreply@anthropic.com>
2026-06-30 10:15:57 -07:00
2 changed files with 81 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
#!/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 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 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.
#
# Isolated = the session sits in a git worktree, where `--git-dir` (…/.git/worktrees/
# <name>) differs from `--git-common-dir` (…/.git). Equal = the shared 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 <branch> ../${repo}-worktrees/<name> 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
# 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")"
fi
+30
View File
@@ -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"
}
]
}
]
}
}