Skip to content

fix(orchestration): treat ask as a long-poll so it survives the 30s socket idle wall#9351

Open
averydev wants to merge 1 commit into
stablyai:mainfrom
averydev:fix/orchestration-ask-long-poll-keepalive
Open

fix(orchestration): treat ask as a long-poll so it survives the 30s socket idle wall#9351
averydev wants to merge 1 commit into
stablyai:mainfrom
averydev:fix/orchestration-ask-long-poll-keepalive

Conversation

@averydev

@averydev averydev commented Jul 18, 2026

Copy link
Copy Markdown

Problem

orchestration.ask blocks server-side until a reply lands or its timeout (default 600 s) elapses — it holds the RPC open exactly like check --wait (src/main/runtime/rpc/methods/orchestration.ts, the ask wait loop). But isLongPollRequest (src/main/runtime/runtime-rpc.ts) only classified terminal.wait and orchestration.check --wait as long-polls. ask fell through to false.

Two consequences follow from that single omission:

  1. The keepalive is never armed. startKeepalive() runs only for long-polls, so ask gets no {"_keepalive":true} frames. The socket's 30 s idle timer (RUNTIME_RPC_SOCKET_IDLE_TIMEOUT_MS, unix-socket-transport.ts) then fires socket.destroy(), and any ask left unanswered for 30 s dies with a misleading runtime_unavailable"The Orca runtime closed the connection before responding"regardless of --timeout-ms.
  2. The handler's disconnect-release path is dead code. The dispatcher passes the abort signal only for long-polls (signal: longPoll ? context?.signal : undefined), so the ask handler's signal?.aborted guard (commented "if the asking client disconnects, release the waiter immediately") never runs.

The ask handler was written expecting long-poll treatment (600 s default block + signal-based disconnect handling); the §3.1 keepalive work was scoped to the reported symptom (check --wait) and never generalized to ask.

Reproduction (live, against the current build)

Call Requested timeout Actually lasted Outcome
ask (before fix) 60 s died at 30 s runtime_unavailable — "runtime closed the connection"
check --wait 45 s ran full 45 s clean {count:0} (keepalives at 15/30/45 s)

Fix

Add orchestration.ask to isLongPollRequest — this arms the keepalive, wires the abort signal, and admits it under the long-poll cap. ask blocks unconditionally, so no --wait-style param gate is needed. The keepalive machinery already exists; ask was simply never opted in.

The client side already handles ask correctly: the CLI ask handler passes an explicit per-call socket timeout (timeoutMs + 5s, src/cli/handlers/orchestration.ts), which is why the failure above was server-side at 30 s rather than a client-side cap. No client change is needed.

Testing

  • New test: emits keepalive frames while orchestration.ask blocks for a reply — drives a real socket, asserts ≥3 keepalive frames and an ok:true / timedOut:true terminal frame. Verified it fails without the fix (0 keepalives) and passes with it.
  • runtime-rpc.test.ts — 48 passed.
  • typecheck (node + cli projects) clean; oxlint clean.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The RPC runtime now classifies orchestration.ask requests as long-poll operations, enabling existing admission, abort, and keepalive handling. A new integration test opens a framed orchestration.ask session, verifies a timed-out terminal response and multiple keepalive frames, and cleans up the orchestration database and RPC server.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the core change and testing, but it omits required Screenshots, AI Review Report, Security Audit, and Notes sections. Add the missing sections, including Screenshots (or 'No visual change'), AI Review Report with cross-platform checks, Security Audit, and Notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: treating orchestration.ask as a long-poll request.

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.

🧹 Nitpick comments (1)
src/main/runtime/runtime-rpc.test.ts (1)

3566-3569: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reorder shutdown sequence and clean up the temporary directory.

It is safer to stop the server before closing the database to ensure that no in-flight requests or shutdown routines attempt to access a closed database connection, which could throw unhandled exceptions. Additionally, consider removing the temporary userDataPath directory to avoid resource leaks across test runs.

♻️ Proposed refactor
       } finally {
-        db.close()
         await server.stop()
+        db.close()
+        rmSync(userDataPath, { recursive: true, force: true })
       }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9184c62e-38c0-46b4-a212-5c5766e82e8f

📥 Commits

Reviewing files that changed from the base of the PR and between d8629c4 and c858168.

📒 Files selected for processing (3)
  • src/cli/runtime/client.ts
  • src/main/runtime/runtime-rpc.test.ts
  • src/main/runtime/runtime-rpc.ts

…ocket idle wall

orchestration.ask blocks server-side until a reply lands or its timeout
(default 600s) elapses, holding the RPC open — but isLongPollRequest never
classified it as a long-poll. So the keepalive that resets the 30s
RUNTIME_RPC_SOCKET_IDLE_TIMEOUT_MS was never armed, and any ask left
unanswered for 30s died with a misleading runtime_unavailable ("The Orca
runtime closed the connection"), regardless of --timeout-ms. The same
omission left the handler's abort signal unwired (it is only passed for
long-polls), so the client-disconnect release path guarded by
signal?.aborted was dead code.

Add orchestration.ask to isLongPollRequest so it gets the keepalive, the
abort signal, and long-poll admission. The client already extends its
per-call socket timeout for ask (handlers/orchestration.ts passes
timeoutMs + 5s at the call site), so only the server-side classification
was missing.
@averydev
averydev force-pushed the fix/orchestration-ask-long-poll-keepalive branch from c858168 to 1cd244d Compare July 18, 2026 16:15
@AmethystLiang AmethystLiang self-assigned this Jul 18, 2026
@AmethystLiang
AmethystLiang self-requested a review July 18, 2026 16:46
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.

2 participants