feat: cross-repo isolation hook and dev agent profile update #215
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/claude-custom!215
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "205-cross-repo-isolation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Prevents parallel Dev agents from clobbering shared checkouts when working on repos other than the spawning repo. Adds a PreToolUse hook that detects
cd ~/repo && git checkoutpatterns on non-spawning repos and blocks with guidance to clone to/tmp/{repo}-{branch}instead. Incident 2026-03-26: three parallel agents all rancd ~/westside-playground && git checkouton the same shared directory, destroying each other's work.Changes
hooks/cross-repo-isolation.sh(NEW) -- PreToolUse Bash hook that pattern-matchescd ~/repo && git checkout/switchcommands, identifies the spawning repo from CWD (including worktree paths), and blocks cross-repo checkouts with a deny + safe alternative message. Lightweight string matching only, no filesystem checks.agents/dev.md-- Added "Cross-Repo Isolation" section with safe/unsafe patterns and rules. Added constraint line: "Nevercd ~/repo && git checkouton a non-spawning repo."hooks/cleanup-worktrees.sh-- Added/tmp/cross-repo clone cleanup section that scans for/tmp/{repo-name}-*directories with.gitolder than 7 days and removes them.settings.json-- Registeredcross-repo-isolation.shin the Bash PreToolUse matcher.Test Plan
echo '{"tool_input":{"command":"cd ~/westside-playground && git checkout -b my-feature"},"cwd":"/home/ldraney/pal-e-platform/.claude/worktrees/agent-abc123"}' | bash hooks/cross-repo-isolation.sh-- fires BLOCKED with safe alternative$HOME/and/home/ldraney/path variants -- all blockedgit switch-- blockedcd ~/pal-e-platformfrom pal-e-platform CWD) -- silent exit 0, allowed/tmp/clone path (cd /tmp/westside-app-branch) -- silent exit 0, allowedls ~/repo) -- silent exit 0, allowedsettings.jsonvalidates as valid JSONReview Checklist
Related Notes
Related
Closes #205
forgejo_admin/pal-e-platform#188claude-custom#184Prevents parallel Dev agents from clobbering shared checkouts when working on repos other than the spawning repo. Adds PreToolUse hook that detects cd ~/repo && git checkout patterns and blocks with guidance to clone to /tmp/{repo}-{branch} instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>QA Review -- PR #215
Files Reviewed
hooks/cross-repo-isolation.sh(NEW, 83 lines)agents/dev.md(24 lines added)hooks/cleanup-worktrees.sh(35 lines added, 5 removed)settings.json(4 lines added)Findings
Correctness: PASS
cd ~/repo && git checkout/switchcommands~/repo/.claude/worktrees/agent-xxx)/tmp/clone paths are correctly excluded (don't match~/,$HOME/, or/home/user/patterns)/tmp/{repo-name}-*with.gitcheck to avoid false positivesSOP Compliance: PASS
set -euo pipefail)settings.jsonunder Bash matcheragents/dev.mdupdated with section and constraintEdge Cases Considered:
git clone ~/repo /tmp/repo-branch && cd /tmp/repo-branch && git checkout -b branch-- ALLOWED (correct: /tmp/ path doesn't match home dir patterns)~/repo/.claude/worktrees/agent-xxx) -- spawning repo correctly resolved via sed stripNits: None
VERDICT: APPROVED
PR #215 Review
DOMAIN REVIEW
Tech stack: Bash hooks for Claude Code harness (PreToolUse pattern matching, SessionStart cleanup), agent profile markdown, JSON configuration. This is shell scripting + configuration management.
hooks/cross-repo-isolation.sh(new, 83 lines)Correctness of pattern matching: The hook correctly uses a two-stage filter -- first a quick exit if
cdandgit checkout|switchare not both present, then a targeted grep for cd targets. This is sound and performant.Path normalization: The sed chain handles
~/repo,$HOME/repo, and/home/*/repoforms. The first sed"s|^~/|$HOME/|"expands tilde, and the second"s|^\$HOME/|$HOME/|"expands literal$HOME. Both are correct in double-quoted context. The grep also captures/home/*/repodirectly. All three variants from the test plan are covered.Spawning repo detection via CWD: The hook strips
.claude/worktrees/...suffix to resolve the root repo name. This correctly handles both direct repo CWD and worktree CWD. Solid.Early exit on first match: The
exit 0inside the while loop means only the first cross-repo cd target triggers the block. If a command has multiple cd targets (unlikely but possible with chained commands), only the first is checked. This is acceptable for the pattern being caught (cd ~/repo && git checkout).set -euo pipefail: Correct. Matches convention used by 12 other hooks in this repo.jq output format: Follows the existing
permissionDecision: "deny"convention established inblock-upstream.sh,block-main-commits.sh, and others. Correct.Potential bypass: A multi-line command or one that uses
pushdinstead ofcdwould bypass this hook. The hook header acknowledges it is "lightweight: pattern match on command string only" which is the right tradeoff for sub-50ms execution. Thepushdbypass is a marginal edge case -- agents don't typically usepushd.agents/dev.mdupdatesThe "Cross-Repo Isolation" section is placed correctly between "Branch Naming Convention" and "Constraints". Clear safe/unsafe examples. The constraint line addition (
Never cd ~/repo && git checkout on a non-spawning repo) is properly placed in the existing constraints list.Documentation matches hook behavior accurately. The safe pattern shows
git cloneto/tmp/, which is exactly what the hook's deny message recommends.hooks/cleanup-worktrees.shchangesThe new
/tmp/cleanup loop iterates overREPO_DIRSand glob-matches/tmp/{repo-name}-*directories. Uses the sameMAX_AGE_DAYSthreshold andstat -c %Yage check as the existing worktree cleanup. Consistent.errorsarray scope: The new code appends to the existingerrorsarray (initialized at line 42), which is correct. Error entries from both worktree cleanup and/tmp/cleanup will appear in the same errors summary section.Summary block rebuild: The original
if [ ${#cleaned[@]} -gt 0 ]; then...else exit 0is replaced withif [ ${#cleaned[@]} -gt 0 ] || [ ${#tmp_cleaned[@]} -gt 0 ]; then. This correctly ensures the hook reports when either type of cleanup happened.Silent exit preserved: The
elsebranch still doesexit 0when nothing was cleaned. Behavior preserved.settings.jsonchangesThe new hook is registered under the
BashPreToolUse matcher, which is correct -- it intercepts bash commands. Placed aftercheck-ruff-before-commit.sh. JSON structure is valid (trailing comma on the preceding entry, new entry with closing bracket).Convention compliance: Issue #166 established that matchers must use separate entries, not pipe-separated. This hook is registered as a standalone entry under the existing
Bashmatcher array -- correct.BLOCKERS
None found.
NITS
cross-repo-isolation.shline 36 -- grep character class for repo names: The regex[a-zA-Z0-9._-]+does not include the@character. If any repo checkout path ever contained@(unlikely for local checkouts, but Forgejo clone URLs can), it would not be captured. This is fine for the current REPO_DIRS list but worth noting.cleanup-worktrees.sh-- glob expansion when no matches exist: The pattern/tmp/"${repo_name}"-*will remain as a literal string if no matches exist. The next line[ -d "$tmp_clone/.git" ] || continuehandles this correctly (the literal glob string is not a directory), but addingshopt -s nullglobbefore the loop and unsetting it after would be cleaner. Minor -- the current code works correctly.agents/dev.mdsafe pattern placeholder: The safe pattern showsgit clone ~/other-repo /tmp/other-repo-{branch}with{branch}as a placeholder. This is clear, but the hook's deny message also uses{branch}as a placeholder. Consistent, which is good.Comment typo in hook header: Line 15 says "Does NOT fire for:" with two bullet points. The second bullet says "Commands that don't include git checkout/switch after a cd to ~/" -- this is slightly misleading since it also does not fire for
/tmp/paths. The comment could mention/tmp/paths explicitly, though the code makes this clear.SOP COMPLIANCE
205-cross-repo-isolationmatches issue #205forgejo_admin/pal-e-platform#188and adjacentclaude-custom#184noted with no-overlap clarificationfeat:)PROCESS OBSERVATIONS
/tmp/clone cleanup was integrated into the existingcleanup-worktrees.shSessionStart hook. This reduces operational complexity -- one hook handles all stale workspace cleanup.VERDICT: APPROVED