afx cleanup -i <issue> reports Bugfix builder not found for issue #N for bugfix builders that afx cleanup -p <n> cleans successfully. Hit twice on 2026-08-13 (bugfix-1433, bugfix-1437); the false not-found was initially misdiagnosed as registry corruption (#1176) and led to two worktrees being removed manually instead of through the sanctioned path.
Root cause (packages/codev/src/agent-farm/commands/cleanup.ts):
- The
--issue branch matches b.id === \bugfix-${issue}`, then falls back to b.issueNumber === issue. Real builder ids carry the builder- prefix (builder-bugfix-1433), so the exact-id match never hits, and the issueNumber` fallback did not match either for these builders.
- The
--project branch has the same exact-id miss but recovers via an additional name-pattern fallback (state.builders.find(b => b.name.includes(projectId))), which the --issue branch does not have.
So the two selectors have asymmetric matching, and the documented one for bugfixes (-i, per afx cleanup --help: 'Cleanup bugfix builder by issue number') is the weaker of the pair.
Wanted: make -i resolve through the same fallback chain as -p (prefix-tolerant id match plus name/worktree pattern), and — since a false 'not found' invites manual worktree deletion — have the not-found message name the alternative selector rather than dead-ending. Worth auditing -t/--task for the same asymmetry (see #1422, where task builders can't be resolved at all for resume/recovery).
afx cleanup -i <issue>reportsBugfix builder not found for issue #Nfor bugfix builders thatafx cleanup -p <n>cleans successfully. Hit twice on 2026-08-13 (bugfix-1433, bugfix-1437); the false not-found was initially misdiagnosed as registry corruption (#1176) and led to two worktrees being removed manually instead of through the sanctioned path.Root cause (
packages/codev/src/agent-farm/commands/cleanup.ts):--issuebranch matchesb.id === \bugfix-${issue}`, then falls back tob.issueNumber === issue. Real builder ids carry thebuilder-prefix (builder-bugfix-1433), so the exact-id match never hits, and theissueNumber` fallback did not match either for these builders.--projectbranch has the same exact-id miss but recovers via an additional name-pattern fallback (state.builders.find(b => b.name.includes(projectId))), which the--issuebranch does not have.So the two selectors have asymmetric matching, and the documented one for bugfixes (
-i, perafx cleanup --help: 'Cleanup bugfix builder by issue number') is the weaker of the pair.Wanted: make
-iresolve through the same fallback chain as-p(prefix-tolerant id match plus name/worktree pattern), and — since a false 'not found' invites manual worktree deletion — have the not-found message name the alternative selector rather than dead-ending. Worth auditing-t/--taskfor the same asymmetry (see #1422, where task builders can't be resolved at all for resume/recovery).