Skip to content

afx cleanup: gate worktree/branch removal on lane completion, not builder type (pir/spir/air always require manual deletion) #1448

Description

@amrmelsayed

afx cleanup auto-removes worktree and branches for bugfix and task builders, but preserves them for pir / spir / air builders, printing manual instructions instead:

const isEphemeral = isBugfixMode || isTaskMode;        // cleanup.ts:252
if (isEphemeral && !isShellMode) { /* remove worktree, delete local + remote branch */ }
else if (!isShellMode) {
  logger.info(`Worktree preserved at: ${builder.worktree}`);
  logger.info('To remove: git worktree remove "' + builder.worktree + '"');
}

The gate is builder type alone — nothing inspects whether the lane finished or whether its PR merged.

Why this is worth revisiting

The preserved-by-default choice presumably protects unfinished spec-driven work. But in practice the architect reaches the cleanup step only after the PR is merged and the protocol is complete, so the safety it buys is usually zero while the cost is real: every completed pir/spir/air lane ends in hand-run git worktree remove --force and git branch -d. Those are precisely the destructive commands the project's own rules put behind human judgment, so the current default routes routine cleanup through the riskiest available path. On 2026-08-12/13 this happened for eight lanes in a single day across two architects.

The safety check already exists

The remote-branch step for ephemeral builders already verifies the PR merged before deleting, and degrades safely when it cannot (cleanup.ts:337-365):

const mergedResult = await executeForgeCommand('pr-search', {
  CODEV_SEARCH_QUERY: `head:${builder.branch} is:merged`,
}, { cwd: config.workspaceRoot });
// no merged PR  -> warn, skip deletion, suggest --force
// open PR       -> warn "has an open PR", skip deletion
// merged        -> deleteRemoteBranch(...)

So the machinery to decide "this lane is genuinely finished" is written, tested in production use, and currently applied to only one of the two branches of the type split.

Proposal

Gate removal on completion, not builder type: for any builder, auto-remove the worktree and branches when the merged-PR verification passes and the worktree is clean, and preserve with today's manual instructions otherwise (unmerged, open PR, uncommitted non-scaffolding changes, or verification unavailable). That keeps every existing protection, removes the routine manual-deletion step, and makes the preserved case meaningful — preservation would then signal "something is actually unfinished here" rather than "this was a spir".

Worth deciding alongside:

  • whether uncommitted scaffolding-only residue (.builder-prompt.txt, .builder-role.md, .builder-session-id, .builder-start.sh, .claude/hooks/) should count as clean, since it is present in every worktree by construction and is the usual reason a lane looks dirty at cleanup time;
  • whether a flag (--keep) should let an architect opt into preservation explicitly.

Supporting instances from the streamdeck architect's lane will be added in a comment; both architects hit this repeatedly on 2026-08-12/13. Related: #1445 (selector asymmetry in the same command), #1446 (a completed project's status.yaml never records its own merge).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/towerArea: Tower server / agent farm CLI

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions