Expected
Closing a project tab removes it from every Grid Dispatch row's project binding (DispatchGridRow.projectTabIds), so no row stays restricted to a project that no longer exists, and New Agent… from such a row's empty lane still creates an agent somewhere sensible.
Actual
closeTab (src/renderer/src/workspace/hook/actions/tab.ts) clears tiled lanes pointing at the killed sessions (clearTiledLaneSessions) but never touches row bindings. The binding scrub, scrubGridRowMetadata, runs only at the persistence boundary (src/renderer/src/workspace/sessionOwnership.ts), which cleans the copy written to disk, not the live state. For the rest of the session a row can stay bound to a closed tab.
Consequences in that state:
- The row's index is blank (it filters to a project that has no agents).
- New Agent… from an empty lane in that row targets the dead tab:
resolveDispatchSpawnTarget returns the bound tab id, createDetachedDispatchAgent hits if (!tab) return null (workspace/hook/actions/pane.ts) — no toast — and the placement overlay stays open because its commit latch is set and only a successful spawn closes it. The user gets no feedback and has to Escape.
Reproduction
- Grid Dispatch with two rows. Bind row 2 to project B only (Row Projects…).
- Close project B's tab.
- Focus an empty lane in row 2 and run New Agent…, pick Claude, press Enter.
Result: nothing is created, no message, the overlay remains until Escape.
Impact
Silent failure of the primary creation command; a confusing blank row index until restart.
Suggested fix
Scrub row bindings in closeTab with the helper that already exists for this (scrubGridRowMetadata), so memory matches what autosave writes. Possibly also toast in createDetachedDispatchAgent's missing-tab branch so any future stale target is visible.
Notes
Found during review of #860 (New Agent In…). That PR makes its own dialog say "None of this row's projects are open" in this state, but does not fix the root cause.
Expected
Closing a project tab removes it from every Grid Dispatch row's project binding (
DispatchGridRow.projectTabIds), so no row stays restricted to a project that no longer exists, and New Agent… from such a row's empty lane still creates an agent somewhere sensible.Actual
closeTab(src/renderer/src/workspace/hook/actions/tab.ts) clears tiled lanes pointing at the killed sessions (clearTiledLaneSessions) but never touches row bindings. The binding scrub,scrubGridRowMetadata, runs only at the persistence boundary (src/renderer/src/workspace/sessionOwnership.ts), which cleans the copy written to disk, not the live state. For the rest of the session a row can stay bound to a closed tab.Consequences in that state:
resolveDispatchSpawnTargetreturns the bound tab id,createDetachedDispatchAgenthitsif (!tab) return null(workspace/hook/actions/pane.ts) — no toast — and the placement overlay stays open because its commit latch is set and only a successful spawn closes it. The user gets no feedback and has to Escape.Reproduction
Result: nothing is created, no message, the overlay remains until Escape.
Impact
Silent failure of the primary creation command; a confusing blank row index until restart.
Suggested fix
Scrub row bindings in
closeTabwith the helper that already exists for this (scrubGridRowMetadata), so memory matches what autosave writes. Possibly also toast increateDetachedDispatchAgent's missing-tab branch so any future stale target is visible.Notes
Found during review of #860 (New Agent In…). That PR makes its own dialog say "None of this row's projects are open" in this state, but does not fix the root cause.