Skip to content

fix(serve): orca serve on orcad serves the browser client and answers plain HTTP - #30120

Merged
nwparker merged 2 commits into
mainfrom
nwparker/serve-web-hang
Oct 17, 2026
Merged

nwparker merged 2 commits into
mainfrom
nwparker/serve-web-hang

Conversation

@nwparker

@nwparker nwparker commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​187 $\color{#cf222e}{\Huge{\mathbf{−}}}$​15 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​172
Prod 10 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​130 $\color{#cf222e}{\Huge{\mathbf{−}}}$​17 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​113

ELI5

orca serve on Linux (and anywhere else it runs on its standalone server, orcad) opened its port but never answered a web browser. Every page request just hung. The server simply had not been told where the browser app's files are. Now orca serve tells it, and a server with no browser app still answers right away with "not found" instead of hanging.

What Changed

Before (v1.4.229, headless Ubuntu): orca serve --port 6768 printed no Web client URL. curl http://127.0.0.1:6768/, /web-index.html and even /does-not-exist all timed out with 0 bytes. WebSocket pairing still worked.

After: the server prints its Web client URL, GET /web-index.html returns the Orca Web page, and a path it does not serve gets a 404.

Mechanism:

  • Since the compatibility launcher (feat(runtime): isolate the server behind a compatibility launcher #26374), orca serve runs on orcad by default, from a copy of the server placed under the user's data directory. Electron --serve found the web client next to its own app files (getBundledWebClientRoot). orcad was never given one, so its HTTP server had no request listener. Node accepts the connection and then never answers plain HTTP. Only WebSocket upgrades are handled.
  • The CLI now finds the app's unpacked out/web (the same lookup Electron serve uses, now shared in src/shared/bundled-web-client-root.ts) and passes it to orcad as --web-client-root <dir>. orcad checks that the directory holds web-index.html. If it doesn't, orcad warns and serves without a web client, so it never advertises a dead URL.
  • ws-transport.ts: a server without a web client now answers plain HTTP with 404 instead of leaving requests hanging. This covers managed SSH/WSL orcad and any other host without the web bundle.
  • New packaging guard config/scripts/verify-packaged-web-client.cjs, run in afterPack. The build fails if app.asar.unpacked/out/web/web-index.html is missing, if any ./assets/... file it loads is missing, or if the unpacked CLI the root is resolved from is absent. orcad runs on plain Node and cannot read inside app.asar, so these files must be unpacked.

Why

  • Pass the app's directory, not a copy in the orcad slot: the web client is about 65 MB and 1,100 files. Copying it into the orcad template (as the mobile page is) would add that much to every installer and to every materialized slot. The slot is always built from the same app install the CLI runs from, so the app's own copy is the right version.
  • A flag, not an environment variable: orcad's terminals and agents inherit its environment, and a flag shows up in orcad --help and in the launch-contract tests. The CLI and the slot always come from the same install, so the new flag cannot reach an older orcad that would reject it.
  • The 404 fallback: the reporter asked for it (question 3), and a hung socket is the worst possible failure for a health check or a browser.

Wire compatibility: nothing paired clients exchange changes. The pairing offer only gains the existing optional webClientUrl, which Electron serve already publishes.

Linked Issue

Fixes #30100

Visual Proof

N/A. The change is in headless server HTTP behavior and has no UI. The curl transcripts are under Testing.

Testing

  • Reproduced on a real Linux host (omarchy, Arch x86_64) with the released v1.4.229 .deb, extracted to /tmp (not installed), using an isolated HOME/XDG dirs and a free port. orca-ide serve reported running on orcad, printed no Web client URL, and curl to /, /web-index.html and /does-not-exist all timed out with HTTP=000, while a WebSocket upgrade returned 101. This matches the report.

  • Unit tests, each failing before the fix:

    • ws-transport-static-web.test.ts: a server without a web client answers /, /web-index.html and /does-not-exist with 404 (they hung before).
    • launch-serve-runtime.test.ts: orca serve hands orcad <appRoot>/out/web.
    • serve-orcad-launch.test.ts: orcad argv carries --web-client-root.
    • orcad-launch-contract.test.ts: --web-client-root is parsed.
    • orcad-command-arguments.test.ts: a root without web-index.html is refused with a warning.
    • verify-packaged-web-client.test.mjs: packaging fails on a missing index, a missing referenced asset, or a missing unpacked CLI.
    • electron-builder-runtime-resources.test.mjs: the afterPack fixtures now carry a web client.
  • Locally: pnpm tc, the full static-analysis job steps (oxlint, anti-slop, code-quality plugins, type-aware, changed-code quality, every ratchet), and unit tests for src/main/runtime/rpc, src/cli/runtime, src/main/startup and src/main/orcad. One test in src/main/orcad, orcad-forked-child-paths, fails on this Mac because of the /private/var realpath of the temp dir. It fails the same way without this change.

  • Live check of the fixed build: see the comment below once the PR's Linux AppImage was run on the same host.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

Review

Codex review (gpt-6.1-sol, high) found two launch tests still expecting four arguments. They are fixed in the second commit. The re-review was clean.

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

  • SSH/WSL managed orcad: unchanged apart from now answering 404 to plain HTTP instead of hanging. Those hosts have no web bundle and never served one.
  • macOS packaged serve stays on Electron (it does not use orcad) and already served the web client. On Windows, orcad gets the same flag, and the path is built with path.join.

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)

… plain HTTP

orcad, the default local orca serve host since the compatibility launcher,
was never handed the app's web client directory, so its HTTP server had no
request listener: every GET hung and no Web client URL was printed (#30100).

The CLI now passes the app's unpacked out/web to orcad via --web-client-root,
a server without a web client answers plain HTTP with 404 instead of hanging,
and packaging fails when the unpacked web client or a file it loads is missing.
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

The packaging hook now validates the unpacked web client and its referenced assets. The CLI resolves the bundled web-client root and passes it to orcad. Orcad validates the supplied root before configuring the RPC server. When no static root is configured, the HTTP server now returns 404 responses instead of running without a request listener.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🟡 Moderate · up to 78c7c

The packaged build can reject a valid browser client, blocking release of the serve fix. Correct the asset check before merging.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 31.58% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 17 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly summarizes the primary change: orca serve on orcad now serves the browser client and responds to plain HTTP requests.
Description check Passed The description is complete and follows the repository template. It explains the user impact, implementation, rationale, linked issue, testing, compatibility considerations, and checklist status.
Linked Issues check Passed Issue [#30100] requires a responsive headless orca serve HTTP endpoint, browser-client serving when the bundle exists, a Web client URL, and a response for invalid routes. serveWithOrcad resolve…
Out of Scope Changes check Passed The changed production files implement the [#30100] launch handoff, root discovery, root validation, HTTP fallback, and packaging checks. The changed tests verify those behaviors. No unrelated product…
Full details: Linked Issues check

Explanation

Issue [#30100] requires a responsive headless orca serve HTTP endpoint, browser-client serving when the bundle exists, a Web client URL, and a response for invalid routes. serveWithOrcad resolves the shared bundled web root and passes it with --web-client-root. orcad validates web-index.html before use. WebSocketTransport installs a 404 request listener when no web client exists. The PR adds tests for argument propagation, root validation, HTTP 404 behavior, and packaged web assets.

Full details: Out of Scope Changes check

Explanation

The changed production files implement the [#30100] launch handoff, root discovery, root validation, HTTP fallback, and packaging checks. The changed tests verify those behaviors. No unrelated production behavior or unrelated test scope is demonstrated.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · 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: 584f6a36-e32e-4608-a07d-f8b1c103fc55
📥 Commits

Reviewing files that changed from the base of the PR and between 9586a3f and 78c7cb5.

📒 Files selected for processing: 17
  • config/electron-builder.config.cjs
  • config/scripts/electron-builder-runtime-resources.test.mjs
  • config/scripts/verify-packaged-web-client.cjs
  • config/scripts/verify-packaged-web-client.test.mjs
  • src/cli/runtime/launch-serve-runtime.test.ts
  • src/cli/runtime/launch.ts
  • src/cli/runtime/serve-orcad-launch.test.ts
  • src/cli/runtime/serve-orcad-launch.ts
  • src/main/orcad/orcad-command-arguments.test.ts
  • src/main/orcad/orcad-command-arguments.ts
  • src/main/orcad/orcad-entry.ts
  • src/main/orcad/orcad-launch-contract.test.ts
  • src/main/runtime/rpc/static-web-client-handler.ts
  • src/main/runtime/rpc/ws-transport-static-web.test.ts
  • src/main/runtime/rpc/ws-transport.ts
  • src/main/startup/main-process-serve.ts
  • src/shared/bundled-web-client-root.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.

/** The relative files web-index.html loads; the page is useless if any is missing. */
function referencedAssets(html) {
const assets = new Set()
for (const match of html.matchAll(/\b(?:src|href)=["']\.\/([^"'?#]+)/g)) {

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 | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
# Inspect the build configuration without running repository code.
rg -n -C 3 'base:|web-index|assetsDir|src=|href=' \
  config/scripts/project-renderer-web-client.mjs vite.web.config.ts

# If a built index is already present, inspect its asset references.
if [ -d out ]; then
  fd 'web-index\.html$' out --exec rg -n 'src=|href=' {}
fi

Repository: stablyai/orca

Length of output: 2172


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- vite.web.config.ts ---'
nl -ba vite.web.config.ts | sed -n '1,55p'
printf '%s\n' '--- src/renderer/web-index.html ---'
nl -ba src/renderer/web-index.html | sed -n '1,180p'
printf '%s\n' '--- project-renderer-web-client.mjs relevant declarations ---'
nl -ba config/scripts/project-renderer-web-client.mjs | sed -n '1,210p'
printf '%s\n' '--- verify-packaged-web-client.cjs ---'
nl -ba config/scripts/verify-packaged-web-client.cjs | sed -n '1,180p'

Repository: stablyai/orca

Length of output: 12207


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- build:web-from-renderer references ---'
rg -n -C 5 -F -- 'build:web-from-renderer' package.json pnpm-workspace.yaml config scripts vite*.config.* src || test "$?" -eq 1
printf '%s\n' '--- renderer build configurations ---'
rg -n -C 4 -e 'outDir|base:|web-index|manifest|renderer' vite*.config.* config/scripts config/build-plugins package.json | sed -n '1,260p'

Repository: stablyai/orca

Length of output: 6962


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- candidate Vite and Electron-Vite configuration files ---'
rg --files -g '*vite*.config.*' -g '*electron*vite*' -g 'electron.vite.*' -g 'package.json' | sort
printf '%s\n' '--- renderer output and web-index configuration ---'
rg -n -C 5 -e 'outDir' -e 'web-index' -e 'base:' -e 'manifest' -e 'renderer' --glob '*vite*.config.*' --glob 'config/scripts/*.mjs' --glob 'config/scripts/*.cjs' --glob 'package.json' . | sed -n '1,320p'

Repository: stablyai/orca

Length of output: 26547


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- root Vite dependencies ---'
rg -n -C 3 -e '"vite"' -e '"electron-vite"' package.json pnpm-lock.yaml | sed -n '1,140p'
printf '%s\n' '--- renderer build runner ---'
nl -ba config/scripts/run-electron-vite-build.mjs | sed -n '1,180p'
printf '%s\n' '--- packaging hook and verifier call ---'
rg -n -C 8 -e 'assertPackagedWebClient' -e 'afterPack' config/electron-builder.config.cjs config --glob '*.cjs' --glob '*.mjs' | sed -n '1,220p'

Repository: stablyai/orca

Length of output: 21459


Match the renderer’s root-relative asset URLs.

build:web-from-renderer copies the renderer build, whose Vite configuration does not set base: './'. Its generated index can therefore contain /assets/... URLs. The verifier matches only ./..., returns no assets, and causes afterPack to fail.

🐛 Suggested fix
--- "a/config/scripts/verify-packaged-web-client.cjs"
+++ "b/config/scripts/verify-packaged-web-client.cjs"
@@ -11,7 +11,7 @@
 /** The relative files web-index.html loads; the page is useless if any is missing. */
 function referencedAssets(html) {
   const assets = new Set()
-  for (const match of html.matchAll(/\b(?:src|href)=["']\.\/([^"'?#]+)/g)) {
+  for (const match of html.matchAll(/\b(?:src|href)=["'](?:\.\/|\/)([^"'?#]+)/g)) {
     assets.add(posix.normalize(match[1]))
   }
   return [...assets]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const match of html.matchAll(/\b(?:src|href)=["']\.\/([^"'?#]+)/g)) {
for (const match of html.matchAll(/\b(?:src|href)=["'](?:\.\/|\/)([^"'?#]+)/g)) {

@nwparker

Copy link
Copy Markdown
Contributor Author

Live verification (PR head 78c7cb5):

  • Packaging guard on a real package: the PR's Linux package job ran afterPack with the new verify-packaged-web-client check and passed. The AppImage it uploaded has resources/app.asar.unpacked/out/web/web-index.html.
  • CLI root resolution on Linux (omarchy): I extracted that AppImage to /tmp and ran its own orca-ide under ELECTRON_RUN_AS_NODE, resolving the app root the way launch.js does. findBundledWebClientRoot returned …/resources/app.asar.unpacked/out/web. The built launch.js passes that path to orcad.
  • orcad from this branch (built with pnpm build:orcad, run on macOS under an isolated ORCA_USER_DATA and port):
    • With --web-client-root, it printed Web client URL: http://127.0.0.1:47613/web-index.html#…. GET / and GET /web-index.html returned 200 text/html (2076 bytes, the Orca Web page), and /does-not-exist returned 404.
    • Without a web root, /, /web-index.html and /does-not-exist returned 404 immediately. v1.4.229 hung on all three.
    • WebSocket upgrades still switch protocols in both cases.
  • Not live-tested: the whole CLI → orcad slot chain from one Linux install. The CI AppImage carries no orcad template, so its orca serve falls back to Electron. Building the template on Arch fails the glibc-floor check. That handoff is covered by launch-serve-runtime.test.ts and serve-orcad-launch.test.ts.

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.

[Bug]: Linux headless: orca serve v1.4.229 accepts WebSocket connections but HTTP requests hang indefinitely (possible missing webClientRoot)

1 participant