Symptom
When a builder is terminated (via afx cleanup -p <id>, the right-click Cleanup Builder action, or any other removal path), the Builders tree shows a transient intermediate state for several seconds:
- Builder is in its correct area group (e.g.
VSCODE or TOWER) — normal state
afx cleanup runs → builder's worktree directory is removed, terminal killed, etc.
- For ~2-5 seconds: the builder reappears under the
UNCATEGORIZED group at the bottom of the Builders tree
- Eventually disappears entirely from the tree
The intermediate UNCATEGORIZED appearance reads as a real reclassification event to the user — they may briefly wonder "why did this builder lose its area?" — when in fact the builder is just being cleaned up.
When it started
After #818 (Builders area grouping) merged. Before #818, builders rendered as a flat list, so a cleanup-in-progress builder briefly appeared in the flat list before disappearing — no surprising classification jump. After #818, the area-grouping projection means cleanup-in-progress builders with missing/empty areas[] get dropped into Uncategorized until they're fully gone.
Plausible root causes (for investigation)
The builder should pick one of these via reproduction with logging, before fixing:
-
Stale overview data during cleanup window — The overview cache returns a OverviewBuilder record where areas: [] for the cleanup-in-progress builder. The groupByArea helper in packages/core/src/area-grouping.ts correctly puts areas: [] into Uncategorized. Fix would be to either filter out cleanup-in-progress builders from the projection, or preserve the original areas[] until the builder is fully removed.
-
Cleanup-induced wire-payload partial state — afx cleanup may emit overview SSE events where the cleanup-in-progress builder has been partially mutated (some fields cleared, others not yet). If areas[] gets cleared first while the builder is still in the overview list, it briefly classifies as Uncategorized.
-
Race between afx cleanup's removal phases — afx cleanup does several things in sequence (kill terminal, remove worktree, drop state.db row). The overview cache refreshes via SSE at various points. A refresh that fires AFTER the worktree directory is removed but BEFORE the OverviewBuilder record is dropped from overview would have areas[] empty (no labels available because the underlying issue lookup may key off the worktree's existence).
-
discoverBuilders disk-scan source skipping areas population during cleanup — Per the pir-883 fix that switched the diff source to disk-scan, discoverBuilders reads .builders/<id>/ to enumerate active builders. If the directory is being removed, discoverBuilders might emit a record with empty areas[] because the underlying issue lookup fails for a partially-cleaned-up builder.
Acceptance criteria
Fix options (pick after diagnosing the cause)
Depending on which root cause is identified:
| If the cause is... |
Likely fix |
| Stale overview data |
Filter cleanup-in-progress builders from the projection (need a cleanupPending field on OverviewBuilder, or a similar signal) |
| Partial wire payload |
Ensure overview SSE events don't emit half-mutated records; mutations should be atomic from the wire's perspective |
| Race between cleanup phases |
Suppress overview refreshes during the cleanup window, OR have discoverBuilders skip directories that are mid-removal |
discoverBuilders issue lookup race |
Fall back to the previous areas[] value when the current lookup fails (cache last-known-good) |
Out of scope
- Animating the transition (fade out from area, etc.) — over-engineering for v1; just suppress the intermediate state
- Persistence of builder metadata after cleanup — cleanup means cleanup; the builder should disappear, not linger
- Cleanup performance optimization — separate concern; this issue is about the UX during the window, not making the window shorter
Why BUGFIX (not PIR)
- Symptom is clear (transient
Uncategorized appearance), root cause is investigatable via logs/timing analysis
- Multiple plausible causes listed — investigation phase resolves which is real
- Fix surface is small (likely <50 LOC at the actual race/data-shape fix site)
- Visual verification at PR time is sufficient — no need for a separate dev-approval gate on the running worktree before PR (BUGFIX assumes the reviewer + CMAP can verify the fix in the diff)
Related
Discovered while
Observing the Builders view during routine cleanup operations on 2026-05-28 after #818 + #885 + #895 area-grouping work landed.
Symptom
When a builder is terminated (via
afx cleanup -p <id>, the right-clickCleanup Builderaction, or any other removal path), the Builders tree shows a transient intermediate state for several seconds:VSCODEorTOWER) — normal stateafx cleanupruns → builder's worktree directory is removed, terminal killed, etc.UNCATEGORIZEDgroup at the bottom of the Builders treeThe intermediate
UNCATEGORIZEDappearance reads as a real reclassification event to the user — they may briefly wonder "why did this builder lose its area?" — when in fact the builder is just being cleaned up.When it started
After #818 (Builders area grouping) merged. Before #818, builders rendered as a flat list, so a cleanup-in-progress builder briefly appeared in the flat list before disappearing — no surprising classification jump. After #818, the area-grouping projection means cleanup-in-progress builders with missing/empty
areas[]get dropped intoUncategorizeduntil they're fully gone.Plausible root causes (for investigation)
The builder should pick one of these via reproduction with logging, before fixing:
Stale overview data during cleanup window — The overview cache returns a
OverviewBuilderrecord whereareas: []for the cleanup-in-progress builder. ThegroupByAreahelper inpackages/core/src/area-grouping.tscorrectly putsareas: []intoUncategorized. Fix would be to either filter out cleanup-in-progress builders from the projection, or preserve the originalareas[]until the builder is fully removed.Cleanup-induced wire-payload partial state —
afx cleanupmay emit overview SSE events where the cleanup-in-progress builder has been partially mutated (some fields cleared, others not yet). Ifareas[]gets cleared first while the builder is still in the overview list, it briefly classifies as Uncategorized.Race between
afx cleanup's removal phases —afx cleanupdoes several things in sequence (kill terminal, remove worktree, drop state.db row). The overview cache refreshes via SSE at various points. A refresh that fires AFTER the worktree directory is removed but BEFORE theOverviewBuilderrecord is dropped from overview would haveareas[]empty (no labels available because the underlying issue lookup may key off the worktree's existence).discoverBuildersdisk-scan source skippingareaspopulation during cleanup — Per the pir-883 fix that switched the diff source to disk-scan,discoverBuildersreads.builders/<id>/to enumerate active builders. If the directory is being removed,discoverBuildersmight emit a record with emptyareas[]because the underlying issue lookup fails for a partially-cleaned-up builder.Acceptance criteria
Uncategorizedappearance during cleanup — cleanup-in-progress builders should either stay in their original area group until they fully disappear, OR disappear immediately without the intermediate statearea/vscode), wait for it to appear in the VSCODE area group, thenafx cleanup -p <id>. The builder should NOT briefly jump to UNCATEGORIZED before disappearingarea/*labels still correctly classify intoUncategorizedin their normal lifecycle (not a side effect of fixing the transient state)Fix options (pick after diagnosing the cause)
Depending on which root cause is identified:
cleanupPendingfield onOverviewBuilder, or a similar signal)discoverBuildersskip directories that are mid-removaldiscoverBuildersissue lookup raceareas[]value when the current lookup fails (cache last-known-good)Out of scope
Why BUGFIX (not PIR)
Uncategorizedappearance), root cause is investigatable via logs/timing analysisRelated
packages/core/src/area-grouping.ts— thegroupByAreaprojection that puts empty-areas[]builders intoUncategorizedpackages/codev/src/agent-farm/servers/overview.ts—discoverBuilders(the disk-scan source) and whereOverviewBuilderrecords are constructedDiscovered while
Observing the Builders view during routine cleanup operations on 2026-05-28 after #818 + #885 + #895 area-grouping work landed.