Released: 2026-05-23
Critical patch on the Jacobean line. Fixes the cross-workspace sibling-architect leak introduced by v3.1.1's #786 lifecycle work — a release-blocker that surfaced within minutes of v3.1.1 going live (the architect's own machine experienced it during post-release verify of a new workspace).
In v3.1.1, opening a workspace OTHER than the one that registered sibling architects caused those siblings to be re-spawned as fresh PTYs in the new workspace. Concrete symptom: shannon had ob-refine registered; opening manazil for the first time showed main + ob-refine + bug-backlog in manazil's terminal list, with new PIDs and real running claude processes none of which belonged to manazil.
state.db.architect had no workspace_path column. The table was global per Tower-daemon (anchored to Tower's startup CWD). v3.1.1's #786 added a launchInstance reconcile loop that iterated this global table and re-spawned every architect it found — without any workspace-scoping check, because the data model didn't carry the scope. Pre-v3.1.1, the table was harmless because nothing iterated it; v3.1.1's new iterate-and-respawn loop turned the global table into an active cross-workspace leak.
Migration v11 (in state.db):
- Add
workspace_path TEXT NOT NULLcolumn - Composite primary key
(workspace_path, id) - Backfill
workspace_pathfromglobal.db.terminal_sessionsviaATTACH DATABASE, usingarchitect.terminal_idas the disambiguation key (deterministic — the stable session UUID), withrole_id LIMIT 1as the fallback only whenterminal_idhas no matching session row - Drop orphans (architects with no current terminal_session row)
- New index
idx_architect_workspaceonarchitect(workspace_path)
All state.ts accessors now require workspace context: getArchitects(workspacePath), setArchitect(workspacePath, …), setArchitectByName(workspacePath, name, …), removeArchitect(workspacePath, name), loadState(workspacePath). launchInstance and other callers thread workspacePath through.
All accessors canonicalize the passed workspacePath via realpathSync at the boundary — so symlinked workspace paths resolve to the same canonical key and the same architect row, regardless of which form the caller used.
The first three iterations of #827 tried to patch individual delete sites (deleteWorkspaceTerminalSessions, PtySession exit handlers, getTerminalsForWorkspace stale-cleanup) with isIntentionallyStopping guards to prevent the leaked rows from being preserved-then-deleted. Each iteration's CMAP caught the next delete site. After four sites and no clear stopping point, the architect-side review escalated to "the architecture is wrong, not the call sites" and the PR rewrote to Option A (schema-level scoping). The result is dramatically simpler — workspace isolation now falls out of the data model rather than depending on a maze of intent-flag guards.
The intentional-stop preservation mechanism from #786 stays — sibling architect rows still survive afx workspace stop + afx workspace start. With Option A, that preservation is now narrowly scoped to ONE concern (skip the architect-row delete on graceful stop), not "protect every site that could leak architects."
A late-cycle finding: an initial version of the migration placed CREATE INDEX idx_architect_workspace ON architect(workspace_path) in LOCAL_SCHEMA, which runs on every db.exec(LOCAL_SCHEMA) BEFORE migrations. On existing v10 databases, the architect table has no workspace_path column yet, so the index creation threw and the entire ensureLocalDatabase initialization aborted — breaking every upgrade install. Caught by the architect's local-install verification before publishing. Fixed by moving the CREATE INDEX out of LOCAL_SCHEMA and into the migration block itself (placed outside the alreadyMigrated guard so both fresh-install and upgrade-install paths land it). PR #834 ships alongside PR #827 in this release.
This bug fix went through 7 iterations of CMAP-driven review across two PRs (#827 + #834). Each iteration's independent architect-side CMAP caught a finding the builder's own CMAP missed: the original race in the cross-workspace fix, then a stop+start regression, then exit-handler delete leakage, then getTerminalsForWorkspace stale-cleanup, then migration disambiguation non-determinism, then path-canonicalization mismatch, then the migration-ordering bug that broke upgrade installs. The two-CMAP discipline (builder runs one, architect runs an independent one) caught real bugs at every iteration — single-CMAP review on this PR would have shipped a regression.
Lesson saved in the architect's working memory: when builder's CMAP and architect's CMAP converge differently, the architect's CMAP isn't sloppiness — it's signal. Run both, trust the conjunction.
None at the user-visible API level. The migration is automatic on first state.db read after upgrade.
For TypeScript consumers of @cluesmith/codev-core or internal state accessors: getArchitects, setArchitect, setArchitectByName, removeArchitect, loadState now require a workspacePath parameter. This is internal API; CLI / dashboard / VSCode extension consumers are unaffected.
npm install -g @cluesmith/codev@3.1.2
afx tower stop && afx tower startIf you were running v3.1.1 with multiple workspaces, this release silently fixes the leak on Tower restart (migration v11 runs automatically). Confirm by opening a previously-unused workspace and verifying its architect list shows only the workspace's own architects.
- M Waleed Kadous (@waleedkadous) — reported the leak within minutes of v3.1.1 install (own machine), drove the 7-iteration CMAP discipline that caught every issue including the migration-ordering release blocker.
- External Codev consumers — implicit pressure on multi-workspace correctness; the bug would have surfaced on every two-workspace adopter eventually.
- Builder working under BUGFIX protocol across PRs #827 and #834.