| name | worktree-isolation |
| description | Conflict-safe git worktree workflow. |
| origin | pi-crew |
| triggers | ["create worktree","parallel workers","isolate edits","cleanup worktree","branch freshness"] |
worktree-isolation
Use this skill for worktree-based execution or cleanup. Git worktrees create isolated working directories that allow parallel code-changing tasks without git conflicts.
How Worktrees Work
A git worktree is a separate working directory linked to the same repository. It has its own:
- Working directory (different path)
- HEAD (can be on a different branch)
- Staged/unstaged changes
But it shares:
- Object database (
.git/objects)
- Refs (branches, tags)
This means creating a worktree is cheap (no clone needed) and fast.
When to Use Worktrees
Use worktree mode when:
- Running parallel implementation workers that modify the same repo
- Isolating risky changes that might need to be discarded
- Running multiple agents on the same codebase simultaneously
- Running a long task that would block other work
Don't use worktree mode when:
- The task is read-only (use scaffold mode instead)
- Only one agent needs to work at a time
- The repository has uncommitted changes (must be clean)
Worktree Lifecycle
1. Creation
Prerequisites:
- Leader repository must be clean (
git status empty)
- Sufficient disk space for worktree directory
Creation flow:
team-runner.ts (workspaceMode: "worktree")
→ prepareTaskWorkspace(manifest, task)
→ assertCleanLeader(repoRoot)
→ git worktree add <path> <branch>
→ linkNodeModulesIfPresent(repoRoot, worktreePath)
→ return { cwd: worktreePath, worktreePath, branch }
Naming convention:
- Branch:
crew/<sanitized-runId>-<sanitized-taskId>
- Path:
.worktrees/<runId>/<taskId>/
- Deterministic from run/task IDs — no user-controlled fragments
Example:
Run: team_20260514092752_218fe358085d7115
Task: 01_explore
Branch: crew/team-20260514092752-218fe358085d7115/01-explore
Path: .worktrees/team-20260514092752-218fe358085d7115/01-explore/
2. Reuse
If a worktree with the same branch already exists, it is reused instead of recreated:
const existing = git(cwd, ["worktree", "list", "--porcelain"]);
if (existing.includes(branch)) {
return { reused: true, worktreePath: parsePath(existing) };
}
Reuse is safe when the worktree's base branch hasn't diverged (checked via branch-freshness.ts).
3. Work in worktree
Each task works in its own worktree directory:
cwd = worktree path
git status shows only that task's changes
- Changes are isolated from other worktrees and the leader
4. Cleanup
On task completion:
- Check dirty state (uncommitted changes)
- If dirty and not forced → preserve (report to operator)
- If clean →
git worktree remove <path>
On force cleanup:
git worktree remove <path> --force
- Removes even if there are changes (but logs a warning)
Safety rules:
- Never force-remove dirty worktrees by default
- Always check
git status before cleanup
- Report worktree paths in events/artifacts for recovery
Stale Worktree Detection
Worktrees can become stale when:
- The base branch has moved
- The run was abandoned mid-task
- Node modules are out of date
Detection approach:
function isStaleWorktree(worktreePath: string, baseBranch: string): boolean {
const current = git(worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]);
const ahead = git(worktreePath, ["rev-list", "--count", `${baseBranch}..HEAD`]);
const behind = git(worktreePath, ["rev-list", "--count", `HEAD..${baseBranch}`]);
return Number(ahead) > 10 || Number(behind) > 0;
}
Cleanup stale worktrees:
git worktree list
git worktree remove .worktrees/stale-task --force
Merge Conflict Strategy
When worktrees complete and changes need to be merged back:
- One owner per file/symbol: Assign each file to exactly one worktree. No two worktrees modify the same file.
- Merge order: If multiple worktrees produce changes, merge in reverse creation order.
- Conflict detection:
git status --porcelain shows conflicts.
- Conflict resolution: Resolve in the leader branch, then continue.
If conflicts occur:
git merge --no-commit <branch>
git add <resolved-files>
git commit -m "Merge branch and resolve conflicts"
Branch Freshness Check
Before reusing a worktree, verify the base branch hasn't diverged:
function checkBranchFreshness(worktreePath: string, baseBranch: string): {
fresh: boolean;
ahead: number;
behind: number;
} {
const status = git(worktreePath, ["status", "--porcelain"]);
if (status.trim()) return { fresh: false, ahead: 0, behind: 0 };
const current = git(worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]).trim();
if (current !== baseBranch) return { fresh: false, ahead: 0, behind: 0 };
const ahead = Number(git(worktreePath, ["rev-list", "--count", `${baseBranch}..HEAD`]));
const behind = Number(git(worktreePath, ["rev-list", "--count", `HEAD..${baseBranch}`]));
return { fresh: ahead === 0 && behind === 0, ahead, behind };
}
Crash Recovery
If a task crashes mid-worktree:
- Find orphaned worktrees:
git worktree list
- Check for abandoned runs in
.crew/state/runs/
- If run is failed/cancelled and worktree is dirty → report to operator
- If run is completed → safe to clean up
Enforcement — Worktree Isolation Gate
Before creating or cleaning up worktrees, verify:
If ANY answer is NO → Stop. Verify worktree safety before proceeding.
Anti-patterns
- Parallel editing same file: Assign one owner per file. Use the task ID in branch names to track ownership.
- Force-removing dirty worktrees: Always report dirty state to operator before cleanup.
- Reusing stale worktrees: Check
branch-freshness.ts before reuse. If base branch moved, recreate instead.
- Storing worktrees outside workspace root: All worktrees must be under
<repo-root>/.worktrees/. Never store outside.
- Worktree name collision: Use deterministic naming from run/task IDs, not user input.
Source patterns
src/worktree/worktree-manager.ts — prepareTaskWorkspace, assertCleanLeader, linkNodeModulesIfPresent, sanitizeBranchPart
src/worktree/cleanup.ts — worktree cleanup logic, dirty state detection
src/worktree/branch-freshness.ts — branch divergence detection
src/runtime/team-runner.ts — workspaceMode handling, worktree passed to task
src/runtime/task-runner.ts — worktreePath in task context
Verification
cd pi-crew
git worktree list
git status --short
node --experimental-strip-types -e "
import { prepareTaskWorkspace } from './src/worktree/worktree-manager.ts';
// Requires clean repo and workspaceMode='worktree'
"
npx tsc --noEmit
node --experimental-strip-types --test test/unit/worktree-manager.test.ts test/integration/worktree-mode.test.ts
npm test