diff --git a/src/components/TerminalView.tsx b/src/components/TerminalView.tsx index d9f539fb8..76b2d3aed 100644 --- a/src/components/TerminalView.tsx +++ b/src/components/TerminalView.tsx @@ -2477,6 +2477,11 @@ function TerminalView({ tabId, paneId, paneContent, hidden }: TerminalViewProps) // close-during-create race and stale deferred re-attach timers. const layouts = appStore.getState().panes.layouts if (!collectAllTerminalIds(layouts).has(tid)) { + log.debug('attach gate declined: terminal not referenced by any layout', { + terminalId: tid, + paneId: paneIdRef.current, + intent, + }) return } const term = termRef.current diff --git a/src/store/terminalDetachMiddleware.ts b/src/store/terminalDetachMiddleware.ts index b61277072..daf714a30 100644 --- a/src/store/terminalDetachMiddleware.ts +++ b/src/store/terminalDetachMiddleware.ts @@ -12,6 +12,15 @@ type PanesStateSlice = { panes: { layouts: Record * reported dead or unrecoverable (terminal.inventory / terminals.changed). * There is no live subscription to release, and the server replies with an * error for terminal.detach on a non-existent terminal — so skip them. + * + * MAINTENANCE WARNING: this skip-list is maintained BY HAND. If you add a + * new reducer/action that removes terminalIds from layouts because the + * SERVER already reported those terminals dead, you MUST register it here — + * otherwise this middleware sends a redundant terminal.detach for each one + * and the server answers with an error (harmless but noisy). Conversely, + * never register an action that removes a LIVE terminal from the layouts: + * skipping it would leak the server-side subscription — the exact bug this + * middleware exists to prevent (see PR #534). */ const skipDetachActionTypes = new Set([ clearDeadTerminals.type,