Repository navigation
afx: removed sibling architect resurrects after Tower recovery — reconciliation trusts persisted row without liveness check #1150
Description
Problem
After a machine crash or Tower recovery event, sibling architects that the user deliberately removed via afx workspace remove-architect --name X come back to life — with their old conversation restored via --resume. The user has to remove them again.
Reported by users 2026-07-08.
Root cause (from subagent investigation 2026-07-08)
removeArchitect (packages/codev/src/agent-farm/servers/tower-instances.ts:1115-1193) structurally does the right thing: sets intentionallyStopping, kills the shellper, deletes both DB rows (architects + terminal_sessions), awaits exit. BUT the DB deletes at tower-instances.ts:1174 and tower-instances.ts:1179 are wrapped in silent try { … } catch { /* best-effort cleanup */ }:
try { setArchitectByName(resolvedPath, name, null); } catch { /* best-effort cleanup */ }
try { _deps.deleteTerminalSession(terminalId); } catch { /* best-effort cleanup */ }The architects row can survive intentional removal via three concerns, combining:
- Silent DB write failure (SQLITE_BUSY beyond 5s timeout, IO error) — user sees
"Removed architect 'X'."even though the row is intact. - WAL fsync lag on OS crash:
packages/codev/src/agent-farm/db/index.ts:34-40opens SQLite withsynchronous = NORMALin WAL mode. On power loss / kernel panic, recently committed transactions in WAL that weren't fsync'd can be lost (SQLite's own docs call this the tradeoff of NORMAL vs FULL). launchInstance's sibling reconciliation loop (tower-instances.ts:687-699) readsgetArchitects(resolvedPath)and unconditionally callsaddArchitect()for each non-main row.addArchitectthen reads the storedsession_idand resumes via--resume. No liveness check, no cross-check againstterminal_sessions.
Net: any residual architects row (from silent write failure, WAL loss, or a partial removal) causes the sibling to come back on the next Tower start — with its old conversation resumed via --resume, which for a stale session id also triggers the RC2 crash-loop.
Fix sketch — two parts
Part 1: remove the silent-fail in removeArchitect
Location: tower-instances.ts:1174-1180
If either DB delete throws, return { success: false, error } and surface it to the user instead of swallowing. Making the user retry when Tower is happy is worse than a false-positive "removed" that doesn't stick.
Part 2: gate sibling reconciliation on a liveness signal
Location: tower-instances.ts:687-699
Before addArchitect() in the reconcile loop, require either:
- A matching
terminal_sessionsrow for the same (workspace, role_id), OR - A
session_idwhose jsonl exists on disk
A row that has neither is a "dead" registration and should be pruned rather than respawned.
Optional (defense in depth)
Switch db/index.ts:40 to synchronous = FULL for architect-write transactions. Small blast radius, low write rate, closes the WAL loss window at OS-crash time.
Scope
packages/codev/src/agent-farm/servers/tower-instances.ts—removeArchitect(surface DB errors) + sibling reconcile loop (liveness gate)- Optional:
packages/codev/src/agent-farm/db/index.ts(synchronous mode for architect writes) - Tests:
- simulate DB write failure during
removeArchitect, verify user gets error notsuccess: true - simulate
architectsrow with no matchingterminal_sessionsrow, verify reconcile prunes rather than respawns - simulate
architectsrow with stalesession_id(jsonl missing), verify reconcile prunes
- simulate DB write failure during
Related
- codev adopt / launchInstance: main architect hijacks an unrelated Claude Code session instead of spawning fresh #1145 (RC1 —
buildResumehas no ownership check) - The RC2 bugfix filed alongside this one (Claude crash-loops on stale
--resume) — surviving architect rows here often carry stale session_ids that trigger RC2's crash-loop as the visible symptom
Activity
amrmelsayed commentedon Jul 8, 2026
On it! Working on this with the PIR protocol (plan + dev-approval gates before PR).
[PIR #1150] Plan draft
[PIR #1150] Plan revised: rank #1118 consolidation as primary resurre…
[PIR #1150] Plan draft
[PIR #1150] Plan revised: rank #1118 consolidation as primary resurre…
[PIR #1150] Plan rechecked post-rebase: reuse #1145 ownership-verific…
[PIR #1150] Gate sibling reconciliation on liveness evidence; prune d…
[PIR #1150] Surface removeArchitect DB delete failures; purge stale r…
[PIR #1150] SQLite synchronous=FULL: close the WAL-loss window for co…
[PIR #1150] Tests: liveness gate, prune vs respawn, removal failure s…
[PIR #1150] Thread: implement phase notes
[PIR #1150] Review + retrospective
[PIR #1150] Thread: review phase notes
[PIR #1150] Address codex consult: purge stale terminal_sessions rows…
Merge pull request #1178 from cluesmith/builder/pir-1150
changelog: removed sibling architects no longer resurrect after Tower…
[PIR #1149] Plan revised: rebase on main, account for merged #1145/#1150