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.