Plan: Persist worktree across phases, cleanup on task completion#
Summary#
Replace the per-phase create/destroy worktree lifecycle with an idempotent getOrCreateWorktree() that reuses existing worktrees. Move cleanup from PhaseRouterService.executePhase() finally block to InternalAdapterService when a task reaches terminal state (complete). This eliminates the root cause of #246 — git branch -D failing on checked-out branches causing permanent stuck loops.
Files#
| File | Action | Description |
|---|---|---|
src/worktree/worktree.service.ts | modify | Replace createWorktree() with idempotent getOrCreateWorktree(): reuse existing worktree, attach existing branch, or fresh-create. Remove aggressive pre-cleanup (lines 78-118). |
src/worktree/worktree.service.spec.ts | modify | Update existing tests and add: worktree-exists-reuse, branch-exists-no-worktree, neither-exists-fresh-create cases. |
src/phase-router/phase-router.service.ts | modify | Call getOrCreateWorktree() instead of createWorktree() (line 260). Remove cleanupWorktree() from finally block (lines 427-438). |
src/phase-router/phase-router.service.spec.ts | modify | Update mock method names. Add test: worktree NOT cleaned up after phase. |
src/internal-adapter/internal-adapter.service.ts | modify | Inject WorktreeService. Call cleanupWorktree() when task status is set to complete in advanceAndEnqueue() (line 361). |
src/internal-adapter/internal-adapter.module.ts | modify | Import WorktreeModule so WorktreeService is available for injection. |
src/internal-adapter/internal-adapter.service.spec.ts | create | Unit tests: worktree IS cleaned up on terminal state, worktree NOT cleaned up on phase advancement. |
Steps#
- Refactor
WorktreeService.createWorktree()→getOrCreateWorktree(): Remove lines 78-118 (aggressive pre-cleanup). Add three-way logic: (a) if worktree dir exists and is valid →git fetch origin, return path; (b) if branch exists but no worktree →git worktree add <path> <branch>(no-b); (c) neither exists →git worktree add -b <branch> <path> origin/master. - Detect valid worktree: Use
git worktree list --porcelainin the repo and check ifworktreePathappears. If the directory exists but is not a valid worktree, log a warning and recreate. - Update
PhaseRouterService: Replacethis.worktree.createWorktree()withthis.worktree.getOrCreateWorktree()at line 260. Remove the entirefinallyblock cleanup (lines 427-438). - Add terminal-state cleanup in
InternalAdapterService: ImportWorktreeModuleininternal-adapter.module.ts. InjectWorktreeServiceinInternalAdapterService. InadvanceAndEnqueue()after settingstatus: 'complete'(line 361), callthis.worktree.cleanupWorktree(task.repo, task.issue)wrapped in try/catch. - Update unit tests for
WorktreeService: RenamecreateWorktree()tests togetOrCreateWorktree(). Add three new test cases for the three-way idempotent logic. Remove tests that assert aggressive pre-cleanup behavior. - Update unit tests for
PhaseRouterService: Update mock to usegetOrCreateWorktree. Remove assertions thatcleanupWorktreeis called after phase execution. Add assertion thatcleanupWorktreeis NOT called. - Create
InternalAdapterServiceunit tests: Add test thatcleanupWorktree()is called when task reachescompletestatus. Add test thatcleanupWorktree()is NOT called on normal phase advancement.
Verification#
npm run test— all unit tests pass with no regressions.npm run lint— zero warnings.npm run build— compiles cleanly.
Risks#
- Concurrent phase invocations on same worktree: Mitigated by the queue's serial-per-task processing — only one phase runs at a time per issue.
- Stale worktree state between phases:
getOrCreateWorktree()runsgit fetch originon reuse, ensuring the worktree has latest refs. The Claude skill is responsible for checking out the correct branch/commit.