fix(remote): preserve structured terminal stream failures - #8254
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds structured runtime subscription results and cleanup handling across IPC and preload layers. Updates terminal multiplexing and shared-control connections to classify recoverable failures, manage reconnects, preserve subscriptions, and fence teardown races. Revises client and server socket liveness to re-probe after delayed ticks. Adds tests covering subscription races, transport recovery, reconnect behavior, callback ordering, and pause-aware heartbeat handling. Includes a design document describing the broader remote terminal recovery architecture. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/main/ipc/runtime-environments.ts (1)
46-51: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider extracting the duplicated
RuntimeEnvironmentSubscriptionStartResulttype to a shared module.This type is defined identically in
src/preload/runtime-environment-subscriptions.ts(lines 28-33). Since the preload already imports from../shared/, extracting it to a shared module would keep the IPC contract in one place and prevent drift.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 2aed13c2-859a-455f-ba32-3fecd2c63cea
📒 Files selected for processing (25)
docs/reference/2026-07-11-remote-runtime-terminal-recovery-design.mdsrc/main/ipc/runtime-environments.test.tssrc/main/ipc/runtime-environments.tssrc/main/runtime/rpc/ws-transport.test.tssrc/main/runtime/rpc/ws-transport.tssrc/preload/runtime-environment-subscriptions.test.tssrc/preload/runtime-environment-subscriptions.tssrc/renderer/src/components/terminal-pane/remote-runtime-pty-transport.test.tssrc/renderer/src/runtime/remote-runtime-terminal-multiplexer.tssrc/shared/remote-runtime-client-error-classification.test.tssrc/shared/remote-runtime-client-error-classification.tssrc/shared/remote-runtime-client.test.tssrc/shared/remote-runtime-shared-control-connection.test.tssrc/shared/remote-runtime-shared-control-connection.tssrc/shared/remote-runtime-shared-control-frame-handler.tssrc/shared/remote-runtime-shared-control-keepalive-refresh.test.tssrc/shared/remote-runtime-shared-control-open.tssrc/shared/remote-runtime-shared-control-reconnect-delay.test.tssrc/shared/remote-runtime-shared-control-reconnect.tssrc/shared/remote-runtime-shared-control-requests.tssrc/shared/remote-runtime-shared-control-state.tssrc/shared/remote-runtime-shared-control-subscription-start.tssrc/shared/remote-runtime-shared-control-types.tssrc/shared/remote-runtime-socket-liveness.test.tssrc/shared/remote-runtime-socket-liveness.ts
CodeRabbit disposition: shared subscription-start result typeNo change in this stack. The result shape currently has exactly two private consumers: the main IPC handler that produces it and the preload decoder that consumes it. Moving the alias into This is worth extracting if the contract gains another consumer or starts evolving independently; for the current two-site, stable union, keeping the recovery PR focused has the lower maintenance cost. |
49e2f39 to
9a65961
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: bfb8eae0-6690-4485-930e-3cac13e9f0a4
📒 Files selected for processing (25)
docs/reference/2026-07-11-remote-runtime-terminal-recovery-design.mdsrc/main/ipc/runtime-environments.test.tssrc/main/ipc/runtime-environments.tssrc/main/runtime/rpc/ws-transport.test.tssrc/main/runtime/rpc/ws-transport.tssrc/preload/runtime-environment-subscriptions.test.tssrc/preload/runtime-environment-subscriptions.tssrc/renderer/src/components/terminal-pane/remote-runtime-pty-transport.test.tssrc/renderer/src/runtime/remote-runtime-terminal-multiplexer.tssrc/shared/remote-runtime-client-error-classification.test.tssrc/shared/remote-runtime-client-error-classification.tssrc/shared/remote-runtime-client.test.tssrc/shared/remote-runtime-shared-control-connection.test.tssrc/shared/remote-runtime-shared-control-connection.tssrc/shared/remote-runtime-shared-control-frame-handler.tssrc/shared/remote-runtime-shared-control-keepalive-refresh.test.tssrc/shared/remote-runtime-shared-control-open.tssrc/shared/remote-runtime-shared-control-reconnect-delay.test.tssrc/shared/remote-runtime-shared-control-reconnect.tssrc/shared/remote-runtime-shared-control-requests.tssrc/shared/remote-runtime-shared-control-state.tssrc/shared/remote-runtime-shared-control-subscription-start.tssrc/shared/remote-runtime-shared-control-types.tssrc/shared/remote-runtime-socket-liveness.test.tssrc/shared/remote-runtime-socket-liveness.ts
🚧 Files skipped from review as they are similar to previous changes (24)
- src/shared/remote-runtime-client-error-classification.test.ts
- src/shared/remote-runtime-shared-control-reconnect-delay.test.ts
- src/shared/remote-runtime-shared-control-open.ts
- src/shared/remote-runtime-client-error-classification.ts
- src/shared/remote-runtime-shared-control-frame-handler.ts
- src/shared/remote-runtime-socket-liveness.test.ts
- src/shared/remote-runtime-shared-control-types.ts
- src/shared/remote-runtime-shared-control-subscription-start.ts
- src/main/runtime/rpc/ws-transport.ts
- src/shared/remote-runtime-client.test.ts
- src/shared/remote-runtime-shared-control-state.ts
- src/main/runtime/rpc/ws-transport.test.ts
- src/main/ipc/runtime-environments.ts
- src/preload/runtime-environment-subscriptions.ts
- src/shared/remote-runtime-shared-control-keepalive-refresh.test.ts
- src/main/ipc/runtime-environments.test.ts
- src/shared/remote-runtime-shared-control-requests.ts
- src/shared/remote-runtime-socket-liveness.ts
- src/preload/runtime-environment-subscriptions.test.ts
- src/shared/remote-runtime-shared-control-reconnect.ts
- src/shared/remote-runtime-shared-control-connection.test.ts
- src/renderer/src/runtime/remote-runtime-terminal-multiplexer.ts
- src/shared/remote-runtime-shared-control-connection.ts
- src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.test.ts
9a65961 to
f68be37
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
aa904e2 to
06d82bc
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: fa62913d-db44-400a-9b22-04699ec18642
📒 Files selected for processing (1)
docs/reference/2026-07-11-remote-runtime-terminal-recovery-design.md
|
Rebased onto rebased #8253 + latest |
2f01457 to
9300477
Compare
Stack status vs merged #9774#9774 covers the recoverable transport-close → pane recovery path, but does not fully replace this PR’s IPC/bridge pieces:
Plan: keep this PR open but narrow to those follow-ups on current Will follow with a slimmed branch or force-push once the narrowed patch is ready. |
9300477 to
c26405c
Compare
…ose tombstones Narrow stablyai#8254 onto current main after stablyai#9774: return ok-union start results across IPC, reconstruct cloneable {code,message} in preload (contextBridge), and tombstone early onClose so late handles are release-only.
Narrow rewrite complete (post-#9774)Force-pushed a single-scope branch onto current Kept only:
Focused tests: Ready for re-review as a small follow-up to #9774. |
|
Associating this PR with related issue(s): #8180 |
1 similar comment
|
Associating this PR with related issue(s): #8180 |
…ose tombstones Narrow stablyai#8254 onto current main after stablyai#9774: return ok-union start results across IPC, reconstruct cloneable {code,message} in preload (contextBridge), and tombstone early onClose so late handles are release-only.
|
Rebased onto current Merged main's pairing-revision/transport-generation guards with this PR's serializable Focused tests: runtime-environments + preload subscriptions 46/46 green. ahead=2 behind=0. |
c26405c to
64c592a
Compare
…ose tombstones Narrow stablyai#8254 onto current main after stablyai#9774: return ok-union start results across IPC, reconstruct cloneable {code,message} in preload (contextBridge), and tombstone early onClose so late handles are release-only.
64c592a to
d409354
Compare
Restack + review guide (community, awaiting maintainer)Rebased onto latest What this PR still uniquely doesRecoverable transport-close → pane recovery largely lives on main (see #9774-era recovery). This PR is not that stack. Residual:
Files (small)
Depends on
Tests (local)
Ready for upstream review; community author cannot merge. |
…ose tombstones Narrow stablyai#8254 onto current main after stablyai#9774: return ok-union start results across IPC, reconstruct cloneable {code,message} in preload (contextBridge), and tombstone early onClose so late handles are release-only.
d409354 to
b6e8557
Compare
b6e8557 to
a481c9e
Compare
…ose tombstones Narrow stablyai#8254 onto current main after stablyai#9774: return ok-union start results across IPC, reconstruct cloneable {code,message} in preload (contextBridge), and tombstone early onClose so late handles are release-only.
Greptile SummaryThis PR replaces thrown IPC errors and bare success shapes in the terminal stream subscribe handler with a shared ok-union type (
Confidence Score: 5/5Safe to merge; all changed code paths are covered by dedicated regression tests and the transport-layer close is already idempotent. The subscribe handler now catches every error path and returns structured data instead of throwing, removing the contextBridge serialization hazard. The tombstone flag correctly gates the post-await continuation — no genuine race in Node.js single-threaded event loop. Idempotent close and tombstone ordering are both covered by new dedicated tests. No previously passing assertions were silently weakened. Files Needing Attention: No files require special attention.
|
| Filename | Overview |
|---|---|
| src/shared/runtime-environment-subscription-start-result.ts | New shared ok-union type for the subscribe IPC return value; simple and correct discriminated union. |
| src/main/ipc/runtime-environments.ts | Subscribe handler restructured to catch all errors and return ok-union data; tombstone flag added to track early remote close before the async handle resolves. |
| src/preload/runtime-environment-subscriptions.ts | Consumer updated to parse ok-union result, release dispatcher on failure paths, and throw plain cloneable error objects; tombstone case correctly handled. |
| src/shared/remote-runtime-client.test.ts | Adds test verifying that subscription.close() is a no-op after the remote side closes, plus closeClient() helper on createSubscriptionServer. |
| src/main/ipc/runtime-environments.test.ts | Comprehensive new tests for structured startup failures, duplicate subscription ids, tombstone, and pairing-changed ok:false; existing tests updated to the new ok-union return shape. |
| src/preload/runtime-environment-subscriptions.test.ts | New tests for preload tombstone ordering, structured failure rejection with plain objects, and unexpected invoke rejection normalization. |
Reviews (2): Last reviewed commit: "test(remote): cover early subscription c..." | Re-trigger Greptile
…ose tombstones Narrow stablyai#8254 onto current main after stablyai#9774: return ok-union start results across IPC, reconstruct cloneable {code,message} in preload (contextBridge), and tombstone early onClose so late handles are release-only.
2806a26 to
1879ace
Compare
…ose tombstones Narrow stablyai#8254 onto current main after stablyai#9774: return ok-union start results across IPC, reconstruct cloneable {code,message} in preload (contextBridge), and tombstone early onClose so late handles are release-only.
1879ace to
ba3ddeb
Compare
…ose tombstones Narrow stablyai#8254 onto current main after stablyai#9774: return ok-union start results across IPC, reconstruct cloneable {code,message} in preload (contextBridge), and tombstone early onClose so late handles are release-only.
ba3ddeb to
e8987fd
Compare
Summary
Remote terminal subscription failures now reach the renderer with their machine code intact instead of collapsing into an English message, and a stream that closes while its handle is still resolving no longer leaves a dead subscription retained in the main process.
runtimeEnvironments:subscribereturns{ ok: true, subscriptionId, requestId } | { ok: false, error: { code, message } }instead of rejecting, so contextBridge can clone the failure{ code, message }data rather than a customErrorsubclass, whose extra properties contextBridge dropsonClosethat lands before the awaited subscribe handle resolves is tombstoned, so the late handle is closed and released rather than storedRefs #12667
Why
Upstream #12667 (
15ef69a81) convertedruntimeEnvironments:callrejections into the structured{ ok: false, error: { code, message } }union, and kept the message-fragment fallback on purpose because "untyped main-handler rejections, subscription-start failures, and older code-less paths still rely on it". This PR is the subscribe-side counterpart: it takes subscription-start failures out of that fallback set by giving them the same coded union.The late-close half is a separate, narrower gap. #12384 guarantees the renderer receives exactly one
closewhen a transport is retired; it does not address the bookkeeping race inside the subscribe handler itself. Onorigin/main,onClosecallsremoteRuntimeSubscriptions.delete(subscriptionId)(src/main/ipc/runtime-environments.ts:178-182). If that fires beforesubscribeRuntimeEnvironmentresolves there is nothing in the map to delete, and the laterremoteRuntimeSubscriptions.set(...)at line 207 then stores a handle whose stream has already closed. Nothing else catches it: a plain stream close neither advances the transport generation nor destroys the sender, so the guards at lines 197-206 all pass. The fix latches asubscriptionClosedflag inonCloseand checks it before theset.Screenshots
No visual change.
Testing
src/main/ipc/runtime-environments.test.ts— 51/51 pass (re-run after rebase onto current main)src/preload/runtime-environment-subscriptions.test.ts— 9/9 passAI Review Report
Independent reviews checked contextBridge serialisation, startup-versus-established ordering, error-then-close deduplication, late subscription handles, and recoverable/fatal classification. Review pruning removed an unconsumed failure subscription id while preserving successful subscription identity. Verdict: spec PASS, code APPROVED.
Security Audit
Reviewed contextBridge data shapes, IPC cleanup, structured authentication/protocol errors, and late-handle disposal. Only cloneable code/message failure data crosses the bridge; no secrets, commands, paths, dependencies, or privileged IPC methods are introduced.
ELI5
Remote terminal stream failures were easy to lose across the IPC boundary: the error code was stripped and only English text survived. Startup results and close signals are now structured, cloneable data, so a recoverable transport close does not look like a hard pane death.