Worktree Lifecycle

Isolation by default. Clean up by rule.

Why worktree isolation matters

  • The conductor's main worktree holds untracked scaffolding: .agentic/, in-flight planning artifacts, loop-state files
  • A subagent running in the same tree can stage and commit conductor files it was never meant to ship
  • This does not surface as a test break - it surfaces as a reviewer asking "why is .agentic/loop-state.json in this PR?" days later
  • Worktree isolation prevents both pollution and cross-engineer commit contamination when parallel spawns share a tree
  • Every engineer, qa-engineer, and release-orchestrator spawn MUST use isolation: "worktree"
The conductor never edits the shippable tree directly - not even for Trivial one-line changes. Only the execution location moves off the primary checkout.

Two classes of worktree

Isolation worktrees
Named worktree-agent-*. Created automatically by the Agent tool when isolation: "worktree" is set on the spawn call. Each parallel subagent gets its own copy of the tree.

Cleanup trigger: once the agent returns and the conductor opens a PR (or confirms no PR is needed), the isolation worktree is redundant. The branch holds the commits. Remove immediately.
Feature worktrees
Named feature/*, fix/*, or chore/*. Created at .agentic/worktrees/<branch-name>.

Cleanup trigger: removed after the PR is merged. The merge (not the PR open) is the trigger.
Two classes, two distinct cleanup triggers. Getting the trigger wrong leaves stale worktrees that accumulate between runs and confuse subsequent sessions.

The isolation mandate

There is no in-place exception. The mandate applies to every shippable-edit spawn:

  • Elevated-risk engineer spawns require isolation (standard case)
  • Trivial-path solo engineer spawns also require isolation - the lightweight posture (no Skeptic, no brief) is preserved; only the execution location changes
  • qa-engineer spawns require isolation
  • release-orchestrator spawns require isolation

The Agent tool creates isolation worktrees automatically when isolation: "worktree" is set on the spawn call. No manual git worktree add is needed or correct for isolation worktrees - that command is for manually-managed feature/subagent worktrees only.

The version floor matters: on Claude Code builds predating the isolated-worktree own-file fix, an isolated engineer self-denies on its own files and deadlocks. The fix is a hard floor for the delegation model.

"Conductor never edits the shippable tree directly" is not a convention. It is the mechanism that prevents scaffolding files from appearing in PRs.

Cleanup: isolation worktrees

Trigger: agent returned output AND conductor has opened a PR (or confirmed no PR needed).

# Verify no uncommitted changes before removing:
git -C <worktree-path> status --porcelain
# If clean (no output), remove the worktree and local branch:
git worktree remove <worktree-path>
git branch -D <branch-name> 2>/dev/null || true
# Safe: the PR is backed by the branch on origin, not this local ref.
# Only the redundant local branch is removed; pushed commits and PR are unaffected.
# If modified tracked files exist, inspect first then force-remove:
# git worktree remove --force <worktree-path>
  • The local branch lingers after worktree remove without an explicit branch -D
  • Force-remove is only safe after confirming nothing important is uncommitted
  • Isolation worktrees with changes persist until the conductor explicitly removes them - subagents do not have hooks
Isolation worktrees with no changes are auto-cleaned by the Agent tool. Those with changes are the conductor's responsibility.

Cleanup: feature worktrees

Trigger: the PR is merged (not when the PR is opened).

gh pr merge <number> --squash --delete-branch
git worktree remove --force <worktree-path>
git branch -D <branch-name>   # if not auto-deleted by --delete-branch
git worktree prune             # clean up any stale metadata
  • --delete-branch on gh pr merge may not auto-delete in all gh CLI versions; the explicit git branch -D is the fallback
  • git worktree prune cleans up stale metadata left over from worktrees removed without the normal command

Do not leave stale worktrees between tasks. Between tasks there should be no active subagent worktrees.

Feature worktrees outlive the PR open state; isolation worktrees do not. That asymmetry is the main source of incorrect cleanup timing.

Session-start prune

Run once at session start in the conductor preflight - not before every subagent spawn. Cache the resolved base branch for the session:

git fetch origin
git worktree prune
# Delete orphaned worktree-agent-* branches not checked out in a live worktree:
git branch | grep 'worktree-agent-' | sed 's/^[* ]*//' | while read b; do
  git worktree list | grep -qF "[$b]" || git branch -D "$b"
done

The branch prune runs alongside it. Three safe signals only - never force-deletes unproven work:

  1. [gone]-upstream branches (merged + remote-deleted via squash + --delete-branch)
  2. Branches fully merged into origin/main
  3. Orphaned worktree-agent-* branches whose worktree no longer exists
  • Re-run the preflight only if the user explicitly switches branches or after 30+ minutes of idle time
  • [gone] is the reliable merged-and-cleaned signal after a history rewrite; ancestry alone misses squash-merged branches
The aggressive per-session prune is a complement to Claude Code's own 30-day orphan sweep, not a replacement. Stale worktrees accumulate between sweeps.

Isolated. Pruned. Clean.

Every spawn contained. Every merge leaves no trace.

github.com/Space-Dinosaurs/DinoStack