Since #12667, a present error code short-circuits classification: a code that is genuinely transient but missing from `RECOVERABLE_CODES` classifies as FATAL. That is the shape that dead-ends terminal panes — #12650 fixed exactly that for a different error, where a transient failure misclassified as fatal unmounted the Reconnect banner and left recreating the session as the only escape.
Today the code and fragment sets agree. Nothing prevented a future code from being added without a matching entry, and the failure would have been silent.
This pins that agreement: for every reachable transport error, a code that classifies fatal must not carry a message that would have classified recoverable. 57 coded pairs plus 8 code-less ones, derived by invoking the producers where possible so a reworded message updates the corpus instead of leaving a stale copy silently passing. The failure message names the offending fragment and says what to do about it.
Enumeration turned up producers beyond the obvious ones — notably the Tailscale-hinted variants, where `runtime-environment-transport-routing.ts` mutates the message on an already-coded error before it crosses IPC, making those distinct corpus members.
Also documented (not asserted, because it is unreachable today): `runtime_rpc_queue_overloaded` is absent from both host passthrough allowlists, so if it ever crossed `mapRuntimeError` it would flatten to `runtime_error` while keeping its "queue is full" message — precisely the dangerous shape. The queue pool is never instantiated on the server dispatcher, so it cannot happen now.
The known exception is pinned rather than silently exempted: a dedicated test records WHY the guard cannot see `remote_runtime_busy` (fatal by code, matching no fragment, so the two sides have nothing to disagree about). If someone rewords a busy message into connection wording, that test fails and points at STA-3479.
Proven non-vacuous by four separate injections. The only production change is two `const` to `export const`.
Errors thrown across Electron's `ipcMain.handle` lose their structured error code — only the message survives. So the renderer classified transport failures by matching substrings of English message text. That is how a queue-overload rejection escaped classification during a remote outage and surfaced as a raw error wall: the code was stripped in transit and its message fragment was not in the recoverable list.
This converts `RemoteRuntimeClientError` and `RuntimeRpcCallQueueOverloadError` rejections from `runtimeEnvironments:call` into the existing structured `{ok:false, error:{code,message}}` response, which the preload already passes through unchanged and `unwrapRuntimeRpcResult` already reconstructs with the code intact. Classification now treats a present code as authoritative and consults message fragments only when there is no code.
The fragment list is deliberately RETAINED as a backstop, not deleted: untyped main-handler rejections, subscription-start failures, and older code-less paths still rely on it.
Proven real rather than cosmetic: a test-only patch applied to unmodified main fails (4 failed / 66 passed) because the code does not survive the boundary today, and passes on this branch.
Independent review specifically chased the risk that a present-but-unrecognized code would now short-circuit to fatal where a message fragment previously rescued it — the shape that dead-ends a pane. It enumerated all 34 reachable code/message pairs and confirmed no pair flips recoverable to fatal, that the newly-serialized code set is closed and client-local, and that host-forwarded codes preserve recoverable classification by design. A differential harness over that corpus was verified non-vacuous by injecting the bad shape.
Nothing crosses the paired-runtime wire: desktop main -> IPC -> preload -> renderer only, reusing an existing response shape, no new fields or opcodes.
The connection-level offline state with a single reconnect affordance remains as STA-3456 follow-up work.
When a remote runtime went unreachable (laptop sleep, Tailscale drop), the UI filled with dozens of repeated timeout errors until it was nearly unusable, and the affected terminal then accepted no input after connectivity returned — leaving "close the session and resume it in a new one" as the only escape.
Four causes, three of which were still live:
- Errors accumulated into one ever-growing surface with no de-duplication or cap.
- Queue-overload rejections lose their structured error code crossing the IPC boundary, so they were never classified as recoverable and surfaced raw.
- A transient failure misclassified as fatal called `recovery.cancel()`, setting the pane to an idle phase — which unmounts the Reconnect banner and makes manual retry, online and resume triggers all no-ops. A true dead end, and the reason recreating the session was the only way out.
- Dismissing an error cleared the surface but not the dedup memory, so an identical fatal error recurring in the same outage was suppressed forever while the pane looked healthy; dedup also compared single lines, so multi-line errors never matched and stacked without bound.
The ordinary reconnect loop was already fixed in v1.4.150/160 — bounded backoff, a Reconnect banner and auto-recovery already ship. This fixes what remained.
Note the fix routes fatal resubscribe failures back through the shared terminal error handler: bypassing it had silently dropped stale-handle re-resolution, terminal-gone retirement, SSH-expired recovery and oversized-snapshot suppression — a stuck-pane regression inside the stuck-pane fix, caught in review and covered by 6 dedicated tests.
Verified: reproductions red on main before the fix; after rebasing onto #11542, reverting the dead-end fix still turns its test red. Follow-up STA-3456 tracks preserving typed error codes across the IPC boundary so classification stops matching message text.