Skip to content

fix(web): mount offline instead of no-save degraded mode when the paired host is unreachable at startup - #26623

Closed
espetro wants to merge 2 commits into
stablyai:mainfrom
espetro:fix/web-offline-startup
Closed

espetro wants to merge 2 commits into
stablyai:mainfrom
espetro:fix/web-offline-startup

Conversation

@espetro

@espetro espetro commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

ELI5

On the web client, the "local" catalog is the paired host over a WebSocket. If that host is unreachable when the app boots (daemon down, port changed, stale entry), the app locked itself into a "session restore failed / changes won't be saved" mode - even though nothing was actually lost - and the user could not add projects, open terminals, or fix the connection from the UI.

What Changed

  • recoverFromDegradedStartup now classifies the thrown error with the existing isRecoverableRemoteRuntimeConnectionError classifier; a recoverable connection error takes a new offline path instead of the no-save path.
  • Offline path: mounts the shell (same ready flags as the degraded path), shows a "Could not reach the Orca host" toast with a Retry now action, and reloads into a clean hydration on the first transport === 'ready' status snapshot - the WS transport already auto-reconnects on its own.
  • Genuine hydration failures (schema, persistence, partial reconnect) keep the existing degraded behavior unchanged.

Before: unreachable host → hydration chain aborts → "Session restore failed" + no-save lock. After: unreachable host → app mounts offline with a retry toast and self-reloads when the host answers.

Why

An unreachable paired host is not a session fault: nothing half-hydrated, so the no-save lock protects nothing. Mounting offline keeps the UI usable (you can fix the connection) and the ready-snapshot reload reuses the transport's own reconnection instead of inventing a second retry loop. Genuine corruption still gets the conservative degraded path, so the safety behavior for real faults is untouched.

Fixes #26620

Visual Proof

N/A for attachments here; behavioral before/after: before, a dead stored host left the web client behind a "Session restore failed" toast with no way to interact; after, the shell mounts with a "Could not reach the Orca host" toast (Retry now) and reloads itself once the host is back. Can attach captures on request.

Testing

  • Pointed a stored environment at a dead port - before: "Session restore failed" + no-save lock; after: offline mount + retry toast, and the app reloads when the host comes back.
  • pnpm tc:web clean.
  • Genuine-failure path re-verified to keep the existing degraded toast/lock (classifier only diverts connection errors).
  • Tested on macOS (host) + browser client; logic is platform-independent renderer code.

AI Disclosure

Authored with an AI coding agent (GLM via Maki); human reviewed and verified the diff.

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Security: no new capability; the reload only triggers on the app's own transport status. Cross-platform: renderer-only, no platform branching. Remote/SSH: this is the paired-host recovery path for web clients. Backwards compatibility: behavior change only for the unreachable-host case previously locked out. Performance: adds one status subscription during offline mode, removed on first ready snapshot or abort.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…ost is unreachable at startup

(cherry picked from commit d41996f)
@espetro

espetro commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

@AmethystLiang three small web-client/orcad fixes from running the orcad-served webapp headless:

  1. feat/orcad-help - orcad --help prints usage instead of Unknown argument (the --bind flag was undiscoverable, and it's the one that makes headless orcad reachable over LAN/Tailscale).
  2. fix/web-mobile-api - the paired web client's window.api.mobile was hardcoded stubs (available: false always); now answers from the host's mobile.* RPCs, so the Mobile pane can actually mint a pairing QR (refs [Bug]: Headless serve mode cannot complete mobile pairing — multiple distinct failures #18504).
  3. fix/web-offline-startup - a web client whose paired host is unreachable at boot mounted into no-save degraded mode with a "Session restore failed" lock; now mounts offline with a retry toast and self-reloads on reconnect.

Each branch is off current main and self-contained. Refs the template's issue-linking requirement: #2 links #18504; #1 relates to #13201; #3 has no matching issue yet - happy to file one if preferred.

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

Walkthrough

Recoverable remote-runtime connection errors now use offline startup recovery instead of degraded no-save recovery. This path marks startup refresh complete, forces workspace readiness, and displays a retry toast. It relaunches when the runtime transport becomes ready. A new healing helper probes the serving origin and, after a successful uncancelled response, stores an endpoint override and rebuilds active runtime clients. Valid overrides are used when resolving the preferred pairing endpoint.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🟡 Moderate · up to dd4cc

Startup recovery now mounts the app offline when it cannot reach the host. Two gaps remain. A busy host whose request queue is full can be treated as unreachable, which can skip the safer degraded mode after partial loading. A different runtime becoming ready can also trigger an early reload. Narrow the offline condition before merging.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the required sections, user-visible behavior, rationale, linked issue, testing, and compatibility considerations. However, it does not provide the required before-and-after visu… Attach before-and-after screenshots or a video showing the previous degraded state and the new offline recovery state. Check the applicable testing items and document why automated tests were not added, or add and report the required lint, …
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: mounting the web client offline instead of entering no-save degraded mode when the paired host is unreachable.
Linked Issues check ✅ Passed The change meets #26620. recoverFromDegradedStartup diverts only errors classified as recoverable remote-runtime connection errors. The new path marks startup refresh complete, forces workspace read…
Out of Scope Changes check ✅ Passed The added startup healer, serving-origin probe, endpoint override storage, and timeout-message classification support the unreachable paired-host cases in #26620. The changes do not add unrelated prod…
Full details: Description check

Explanation

The description covers the required sections, user-visible behavior, rationale, linked issue, testing, and compatibility considerations. However, it does not provide the required before-and-after visual proof for this UI behavior change, and it leaves required testing checklist items unchecked without explaining the omissions.

Resolution

Attach before-and-after screenshots or a video showing the previous degraded state and the new offline recovery state. Check the applicable testing items and document why automated tests were not added, or add and report the required lint, typecheck, test, and build verification.

  • Fix all pre-merge checks with AI
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d1b9a04a-6719-4fdb-b9d6-c342dfaaff01
📥 Commits

Reviewing files that changed from the base of the PR and between 907cb6e and 5def7fd.

📒 Files selected for processing (1)
  • src/renderer/src/startup/startup-degraded-recovery.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

Comment on lines +151 to +154
const unsubscribe = window.api.runtimeEnvironments.onStatusChanged((snapshot) => {
if (snapshot.transport === 'ready') {
unsubscribe()
void window.api.app.relaunch()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
# Inspect the status snapshot identity and the channel's event producers.
rg -n -C 5 'RuntimeHostStatusSnapshot|RUNTIME_HOST_STATUS_CHANNEL|onStatusChanged\(' src

Repository: stablyai/orca

Length of output: 41725


🏁 Script executed:

set -eu
printf '%s\n' '--- snapshot declaration ---'
rg -n -F -- 'export type RuntimeHostStatusSnapshot' src/shared src/main src/preload src/renderer
sed -n '1,240p' src/shared/runtime-host-status.ts

printf '%s\n' '--- channel and event producers ---'
rg -n -F -- 'RUNTIME_HOST_STATUS_CHANNEL' src/main src/preload src/shared src/renderer
rg -n -F -- 'onStatusChanged' src/main src/preload src/renderer
rg -n -F -- 'publish' src/main src/shared/runtime-host-status.ts | head -80

printf '%s\n' '--- startup recovery and callers ---'
sed -n '1,230p' src/renderer/src/startup/startup-degraded-recovery.ts
rg -n -C 8 -F -- 'recoverFromDegradedStartup' src/renderer src/main src/preload

Repository: stablyai/orca

Length of output: 32218


🏁 Script executed:

set -eu
printf '%s\n' '--- main status owner ---'
sed -n '1,180p' src/main/ipc/runtime-environment-status-owner.ts
rg -n -C 8 -F -- 'RuntimeHostStatusOwner' src/main src/renderer src/preload

printf '%s\n' '--- web runtime status API ---'
sed -n '1,180p' src/renderer/src/web/preload-api/web-runtime-environments-api.ts
rg -n -C 10 -F -- 'subscribeWebRuntimeStatus' src/renderer/src/web src/main src/preload

printf '%s\n' '--- web entrypoints and startup recovery binding ---'
rg -n -C 8 -F -- 'startup-degraded-recovery' src/renderer/src/web src/renderer/src
rg -n -C 8 -F -- 'recoverFromUnreachableStartup' src
rg -n -C 8 -F -- 'web-runtime-environments-api' src

Repository: stablyai/orca

Length of output: 39406


🏁 Script executed:

set -eu
printf '%s\n' '--- status owner implementation ---'
sed -n '1,260p' src/shared/runtime-host-status-owner.ts

printf '%s\n' '--- status owner lifecycle ---'
sed -n '36,150p' src/main/ipc/runtime-environment-request-connections.ts
rg -n -C 6 -F -- 'getRuntimeEnvironmentStatusOwner(' src/main/ipc src/main

Repository: stablyai/orca

Length of output: 29652


Filter ready snapshots by the paired environment.

The Electron status channel delivers snapshots from every runtime status owner. This listener checks only snapshot.transport === 'ready', so another environment becoming ready can relaunch the still-offline app. Pass the paired environment ID into this recovery path and require snapshot.environmentId to match before relaunching.

…artup

When the stored WebSocket endpoint is unreachable but the page itself was
served by an orcad, probe ws(s)://<origin> with the stored device token and
pinned host key — an authenticated status.get is the identity proof. The
redirect is kept in a separate endpoint-override store because the
server-store merge resurrects stale stored endpoints; mutating endpoints[]
gets clobbered back to the dead address.

Also classify the code-less 'Timed out while connecting to the remote Orca
runtime' waiter error as recoverable so hydration reaches the offline+heal
path instead of the no-save degraded lock.

(cherry picked from commit 898ee9f)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟠 Major · Restrict offline recovery to connection unavailability. · startup-degraded-recovery.ts:68

src/renderer/src/startup/startup-degraded-recovery.ts:68
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Restrict offline recovery to connection unavailability.

isRecoverableRemoteRuntimeConnectionError also accepts runtime_rpc_queue_overloaded. If startup receives that error after hydration begins, this branch can skip degraded recovery and mark the partially hydrated workspace ready as though the host were unreachable. Keep queue-overload and partial-hydration failures on the existing degraded path.


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 79ec8472-e490-4ab6-af97-3baf85442f96
📥 Commits

Reviewing files that changed from the base of the PR and between 5def7fd and dd4ccf0.

📒 Files selected for processing (5)
  • src/renderer/src/startup/startup-degraded-recovery.ts
  • src/renderer/src/startup/startup-unreachable-host-heal.ts
  • src/renderer/src/web/web-runtime-endpoint-overrides.ts
  • src/renderer/src/web/web-runtime-environment.ts
  • src/shared/remote-runtime-client-error-classification.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@nwparker

nwparker commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Closing as superseded — the linked issue (#26620) has been resolved on main by #29460 (861619fbc2). Thanks for the contribution!

@nwparker nwparker closed this Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Web client locks into no-save degraded mode when the paired host is unreachable at startup

2 participants