Skip to content

Fix #883: close builder terminal tabs on cleanup by diffing overview, not state#892

Merged
amrmelsayed merged 16 commits into
mainfrom
builder/pir-883
May 27, 2026
Merged

Fix #883: close builder terminal tabs on cleanup by diffing overview, not state#892
amrmelsayed merged 16 commits into
mainfrom
builder/pir-883

Conversation

@amrmelsayed
Copy link
Copy Markdown
Collaborator

PIR Review: Close builder terminal tabs on cleanup (read overview, not state)

Fixes #883

Summary

The 3.0.6 fix for "builder terminal tabs close automatically on cleanup"
regressed because the present→absent diff was reading from the wrong
source. This PR points the diff at overviewCache.getData().builders
(disk scan via discoverBuilders) instead of
client.getWorkspaceState().builders (runtime registry rebuilt from
SQLite terminal_sessions), so the diff sees the absence the moment
afx cleanup removes the worktree directory — even while the
companion Tower-side bug (#783) leaves surviving shellper processes
pinning the SQLite-backed source open.

Files Changed

  • codev/plans/883-vscode-builder-cleanup-no-long.md (+216 / -0) — plan artifact
  • codev/state/pir-883_thread.md (+79 / -0) — protocol thread log
  • packages/vscode/src/prune-builder-terminals.ts (+62 / -0) — new pure helper module
  • packages/vscode/src/extension.ts (+21 / -34) — diff wiring switched to overview cache
  • packages/vscode/src/__tests__/prune-builder-terminals.test.ts (+116 / -0) — 11 vitest cases
  • codev/projects/883-vscode-builder-cleanup-no-long/status.yaml (+22 / -0) — porch state

Commits

Test Results

  • pnpm --filter codev-vscode check-types: ✓ pass
  • pnpm --filter codev-vscode lint: ✓ pass
  • pnpm --filter codev-vscode test:unit (vitest): ✓ pass (49 tests, 11 new)
  • pnpm --filter codev-vscode test (mocha integration): ✓ pass (83 tests)
  • porch done 883 checks: ✓ build (5.3 s), ✓ tests (20.5 s)
  • Manual verification at the dev-approval gate: human approved

Architecture Updates

No arch.md changes — the diff swaps one data source for another at the
same architectural boundary (VSCode extension reading from Tower).
There's no new module, pattern, or boundary to document. The new
prune-builder-terminals.ts helper is a vscode-free sidecar so a unit
test can import it without mocks; that's a testability detail, not an
architectural one.

Lessons Learned Updates

Added one entry to codev/resources/lessons-learned.md under Critical:

When a VSCode-side observer needs to react to a Tower-side cleanup,
source the signal from the disk-truth endpoint (/api/overview,
backed by discoverBuilders' readdirSync(.builders/) scan), not the
runtime-registry endpoint (/api/state, backed by SQLite
terminal_sessions reconciled against surviving shellpers). Detached
shellper processes are designed to outlive Tower restarts and can
therefore pin runtime-registry rows open through Tower's
reconnect-on-the-fly path indefinitely. The filesystem state collapses
to "did afx cleanup remove this worktree?" — which is the actual
cleanup signal — so a disk-sourced diff is resilient to the orphan-
shellper class of bugs (cf. #783) by construction.

This is a generalizable rule: any cleanup-detection diff in the
extension should default to the disk-scan source, and pick the
registry source only when worktree-existence isn't the right signal.

Things to Look At During PR Review

How to Test Locally

For reviewers pulling the branch:

  • View diff: VSCode sidebar → right-click builder pir-883 → View Diff
  • Run dev server: VSCode sidebar → Run Dev Server, or afx dev pir-883
  • Build + install: pnpm build && pnpm -w run local-install (restarts Tower, picks up the patched extension)
  • What to verify:
    • Spawn a fresh bugfix builder (afx spawn <issue> --protocol bugfix),
      open its terminal in VSCode, then afx cleanup -p <id> from a
      separate shell. Tab disappears within ~5 s of the cleanup printing
      Builder ... cleaned up!.
    • Repeat via VSCode's right-click → Cleanup Builder for the
      second variant.
    • Optionally start a dev terminal (right-click → Run Dev Server)
      before cleanup, then confirm its (dev) tab also disappears.
    • Spawn a fresh builder after cleanup; its terminal opens normally
      (no map-state pollution from the closure).
    • Sanity probe: sqlite3 ~/.agent-farm/global.db "SELECT role_id FROM terminal_sessions WHERE type='builder'" will likely still list
      cleaned-up builders (afx cleanup can't reach orphaned Tower terminals after porch done self-completion (registry-keyed cleanup, no fallback) #783 left unfixed by design). The VSCode tab
      still closes — that's the point.

Related

The previous diff source (client.getWorkspaceState) is rebuilt from
SQLite terminal_sessions, which surviving shellper processes keep alive
after afx cleanup — so the present-to-absent transition never fired.
overviewCache.getData() comes from a readdirSync(.builders/) scan and
collapses to worktree-on-disk existence, which is the authoritative
"this builder still exists" signal.

Extracts the diff into a vscode-free helper module so a vitest unit
covers the cleanup transition end to end.
@amrmelsayed
Copy link
Copy Markdown
Collaborator Author

Architect Review

Low-risk fix that addresses the actual root cause, not just the symptom. 654/34 across 8 files, but most is artifacts: 3 bookkeeping (plan + thread + status.yaml), 1 new test file (+116 LOC, 11 cases), 1 new pure helper module (+62 LOC). Real change to existing code is -13 net LOC in extension.ts — the fix simplified the diff logic, not bloated it.

CMAP unanimous (gemini=APPROVE, codex=APPROVE, claude=APPROVE).

Verified

  • Right root cause identified. The diff was reading from client.getWorkspaceState().builders (SQLite-backed runtime registry) which doesn't reliably update on cleanup. PR switches the source to overviewCache.getData().builders (disk scan via discoverBuilders reading .builders/<id>/ directories). Disk scan is always up-to-date — the moment afx cleanup removes the worktree directory, the diff sees the absence.
  • Robust to the sibling Tower-side bug. Even if Tower's terminal_sessions table has stale entries (a known issue tracked separately as afx cleanup can't reach orphaned Tower terminals after porch done self-completion (registry-keyed cleanup, no fallback) #783), the disk-scan source ignores them. The fix is independent of whether afx cleanup can't reach orphaned Tower terminals after porch done self-completion (registry-keyed cleanup, no fallback) #783 ever gets resolved
  • Net code reduction in extension.ts (+21 / -34 = net -13) — the rewrite simplified the diff logic, didn't add complexity
  • Pure helper extracted to prune-builder-terminals.ts so the test file can import without mocks. Right testability choice — 11 unit cases cover the diff logic in isolation
  • Plan-approval + dev-approval gates already passed (human verified the running worktree)

Notable design choice — the lessons-learned entry

The builder added a Critical-tier entry to codev/resources/lessons-learned.md:

"When a VSCode-side observer needs to react to a Tower-side cleanup, source the signal from the disk-truth endpoint (/api/overview, backed by discoverBuilders' readdirSync(.builders/) scan), not the SQLite-backed runtime registry..."

This generalizes correctly. The same lesson applies anywhere extension-side code observes Tower state to react to cleanup events — there's likely more than one place this matters. Good capture for future work.

Cross-cutting note

This fix exposes how the 6 plausible causes I listed in the issue body were not equally weighted. The actual cause was a variant of cause #2 (Tower's state.builders not dropping cleaned-up rows), but the builder's fix is structurally better than what cause #2 suggested — instead of trying to fix the SQLite-backed source (which would couple to #783's outcome), they switched to the disk-scan source that's already correct. That's a stronger answer than the issue body anticipated; worth keeping in mind for future "multiple plausible causes" issue framings — sometimes the right fix is to pick a different data source entirely.

Verdict

Approved. Per PIR protocol, the pr gate is the human's call — awaiting your explicit porch approve 883 pr --a-human-explicitly-approved-this before the builder merges.


Architect review

@amrmelsayed amrmelsayed merged commit 344c685 into main May 27, 2026
6 checks passed
amrmelsayed added a commit that referenced this pull request May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vscode: builder cleanup no longer closes the terminal tab — regression of 3.0.2 behavior

1 participant