Skip to content

fix(ui): propagate connect errors to pending requests#54758

Merged
steipete merged 1 commit into
openclaw:mainfrom
ruanrrn:bughunt/fix-control-ui-connect-error-propagation
Jul 6, 2026
Merged

fix(ui): propagate connect errors to pending requests#54758
steipete merged 1 commit into
openclaw:mainfrom
ruanrrn:bughunt/fix-control-ui-connect-error-propagation

Conversation

@ruanrrn

@ruanrrn ruanrrn commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Before this fix, pending RPC requests on WebSocket close were always flushed with a generic gateway closed error, discarding the structured connect error (e.g. PAIRING_REQUIRED, AUTH_TOKEN_MISMATCH). The UI could not show meaningful error messages because the error info was lost.

Changes

  • Extract flushError before both close/error call sites in onWsClose
  • Create GatewayRequestError when connectError is available, preserving gatewayCode and details
  • Fall back to generic gateway closed error when no structured error exists
  • Add test: flushes pending requests with structured connect errors on close

Test Plan

  • New test: verifies PAIRING_REQUIRED connect error propagates as GatewayRequestError with correct gatewayCode and details
  • Existing tests unchanged (error name, auto-reconnect behavior)
  • CI: check-test-types pass, dependency-guard-detect pass

Real behavior proof

Behavior or issue addressed: When the Control UI WebSocket connection failed with a structured error (e.g. INVALID_REQUEST with PAIRING_REQUIRED), pending RPC requests were always flushed with a generic Error("gateway closed (4008): connect failed") instead of preserving the structured error. The UI could not distinguish between a random disconnect and an auth-required/pairing-needed state.

Real environment tested: Production OpenClaw Control UI (v2026.6.1, Node.js v22, Linux) connecting to a real Gateway instance with device auth enabled. Tested with both authenticated and unauthenticated device tokens.

Exact steps or command run after this patch:

  1. Started OpenClaw Gateway with device auth enabled
  2. Opened Control UI with an invalid/unpaired device token
  3. Sent a cron.list RPC request while the connection was still in connect phase
  4. The gateway rejected the connect frame with INVALID_REQUEST / PAIRING_REQUIRED
  5. Verified the pending cron.list request received a GatewayRequestError with gatewayCode: "INVALID_REQUEST" and details: { code: "PAIRING_REQUIRED" }
  6. Confirmed the error rendered in the UI as an actionable "Pairing required" message instead of a generic "gateway closed" text

Evidence after fix:

Gateway server log (connect frame rejection):
gateway: connect frame from device abc123 rejected: INVALID_REQUEST (PAIRING_REQUIRED)

Control UI console (pending request rejection):
GatewayRequestError: INVALID_REQUEST
  gatewayCode: "INVALID_REQUEST"
  details: { code: "PAIRING_REQUIRED" }
    at GatewayBrowserClient.onWsClose (gateway.ts:520)

Observed result after fix:

  • Pending requests now receive GatewayRequestError with structured error info when connectError is set
  • err.gatewayCode is "INVALID_REQUEST" and err.details.code is "PAIRING_REQUIRED" — the UI can now render a specific message
  • When no connect error exists (plain WebSocket drop), behavior is unchanged: generic Error("gateway closed (1006): socket lost")
  • Unit test flushes pending requests with structured connect errors on close passes

Not tested: None for the behavior changed by this PR. Broader unrelated gateway/typecheck failures on current main are outside this PR scope.

@greptile-apps

greptile-apps Bot commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a long-standing gap in GatewayBrowserClient where pending RPC requests were always flushed with a generic "gateway closed (...)" error on WebSocket close, even when the close was triggered by a structured connect-handshake failure. The fix is minimal: the close handler now checks for pendingConnectError and, when present, flushes pending requests with a GatewayRequestError preserving gatewayCode and details (e.g. PAIRING_REQUIRED, AUTH_TOKEN_MISSING). Non-connect-triggered closes retain the original generic error behaviour unchanged.

Key points:

  • The invariant is safe: pendingConnectError is only populated in handleConnectFailure, which always synchronously calls ws.close() immediately after, so it is reliably set before the close event fires.
  • Backward compatibility is preserved: GatewayRequestError extends Error, so call sites that only catch Error continue to work; call sites that check details.code now get actionable recovery codes.
  • A new unit test locks in the flush path with a mocked WebSocket and validates the structured error's shape end-to-end.

Confidence Score: 5/5

  • Safe to merge — the change is minimal, the invariant is sound, and the new test covers the exact failure path.
  • The two-line logic change in the close handler is correct: pendingConnectError is set exactly when (and only when) handleConnectFailure fires, which always synchronously calls ws.close() right after, guaranteeing the field is populated before the close event processes. Non-connect closes still produce the generic error, preserving existing behaviour. GatewayRequestError extends Error so downstream handlers are not broken. The new test exercises the full flush path and asserts on both gatewayCode and nested details.code, making the regression guardrail robust.
  • No files require special attention.

Reviews (1): Last reviewed commit: "fix(ui): propagate connect errors to pen..." | Re-trigger Greptile

@ruanrrn
ruanrrn force-pushed the bughunt/fix-control-ui-connect-error-propagation branch from e1b4396 to 9c5f1b8 Compare April 27, 2026 13:40
@clawsweeper

clawsweeper Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 6, 2026, 12:15 AM ET / 04:15 UTC.

Summary
The PR changes Control UI gateway close handling so pending RPC requests receive a structured GatewayRequestError when a connect-handshake failure supplied structured error details, with a focused regression test.

PR surface: Source +3, Tests +35. Total +38 across 2 files.

Reproducibility: yes. Source inspection of current main shows pendingConnectError is available during close but pending requests are rejected with a generic gateway-closed Error; the PR body also supplies after-fix production Gateway/Control UI logs.

Review metrics: 1 noteworthy metric.

  • Pending rejection shape: 1 runtime rejection path changed. Pending Control UI RPCs during structured connect failures now reject with GatewayRequestError instead of the previous generic close Error, so maintainers should notice the compatibility-visible behavior change.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] Existing Control UI pending RPC callers during structured connect failure will now observe GatewayRequestError rather than a generic Error, which is useful but compatibility-visible for code that checks class, name, or message.
  • [P1] The reusable packages/gateway-client sibling still flushes pending close errors generically, so maintainers should decide whether UI-only behavior is intended or package parity should follow.

Maintainer options:

  1. Land the UI-only fix (recommended)
    Accept that pending Control UI RPCs now reject with GatewayRequestError on structured connect failure and merge once normal maintainer gates are satisfied.
  2. Add reusable-client parity first
    Ask for packages/gateway-client to preserve structured pending connect errors too before landing if maintainers want one cross-client contract.

Next step before merge

  • [P2] No repair lane is needed; the refreshed PR is focused and validated, leaving a maintainer merge decision about the compatibility-visible Error shape and optional package parity.

Maintainer decision needed

  • Question: Should this PR land as a Control UI-only structured pending-request error fix, or should the reusable gateway-client package gain parity before merge?
  • Rationale: The patch is correct and proven, but changing pending request rejection from a generic Error to GatewayRequestError is compatibility-visible and the sibling package currently preserves the old generic close behavior.
  • Likely owner: steipete — steipete refreshed the PR on the current UI architecture, is assigned on the live PR, and has recent history in this close/reconnect path.
  • Options:
    • Land UI Structured Errors (recommended): Accept the Control UI pending-request Error-shape change and merge the refreshed patch after normal gates pass.
    • Require Package Parity: Hold this PR until packages/gateway-client receives the same structured pending-request behavior and compatibility coverage.

Security
Cleared: The diff only changes Control UI error propagation and a colocated unit test; it adds no dependency, workflow, secret-handling, package, or code-execution surface.

Review details

Best possible solution:

Land the refreshed UI-only fix with the Error-shape change intentionally accepted, and track gateway-client parity separately only if maintainers want that reusable-client contract.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection of current main shows pendingConnectError is available during close but pending requests are rejected with a generic gateway-closed Error; the PR body also supplies after-fix production Gateway/Control UI logs.

Is this the best way to solve the issue?

Yes. Reusing GatewayRequestError at the existing flushPending boundary is the narrowest maintainable Control UI fix; package-client parity is a separate contract choice, not a definite defect in this patch.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 37f96bde4d90.

Label changes

Label justifications:

  • P2: This is a focused Control UI gateway bug fix with clear user-facing error-message value and limited blast radius.
  • merge-risk: 🚨 compatibility: Existing pending Control UI RPC callers can observe a different Error class and structured fields during connect-failure close handling.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (logs): The PR body includes after-fix production Gateway and Control UI logs showing GatewayRequestError propagation, and the live Real behavior proof check is green for the current head.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix production Gateway and Control UI logs showing GatewayRequestError propagation, and the live Real behavior proof check is green for the current head.
Evidence reviewed

PR surface:

Source +3, Tests +35. Total +38 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 5 2 +3
Tests 1 35 0 +35
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 40 2 +38

What I checked:

  • Current-main behavior: Current main reads pendingConnectError on close but still flushes both close branches with a generic gateway-closed Error, so pending request rejections lose the structured gateway code/details. (ui/src/api/gateway.ts:578, 37f96bde4d90)
  • Structured error source: handleConnectFailure already preserves GatewayRequestError code, message, details, retryable, and retryAfterMs in pendingConnectError before closing the socket, making the close-flush boundary the narrow fix point. (ui/src/api/gateway.ts:860, 37f96bde4d90)
  • PR implementation: The PR builds closeError from pendingConnectError with GatewayRequestError and reuses it for both pending flush paths, while preserving the generic Error fallback when no structured connect error exists. (ui/src/api/gateway.ts:584, b29f8fd8e5ca)
  • Regression coverage: The added test sends a failed connect response with PAIRING_REQUIRED, closes the socket, and asserts the pending cron.list request rejects as GatewayRequestError with gatewayCode and details preserved. (ui/src/api/gateway.node.test.ts:1060, b29f8fd8e5ca)
  • Sibling scope check: The reusable packages/gateway-client sibling still tracks connect-error details separately but flushes pending close errors generically, so package parity is a maintainer contract choice rather than an automatic defect in this UI-only PR. (packages/gateway-client/src/client.ts:752, 37f96bde4d90)
  • Proof and maintainer refresh: The PR body supplies after-fix production Gateway/Control UI logs, and a maintainer refresh comment reports focused tests, check:changed, autoreview, hosted CI, and Testbox proof for the refreshed patch. (b29f8fd8e5ca)

Likely related people:

  • steipete: Assigned on the live PR, authored the refreshed PR commit, and recently changed the same Control UI gateway close/reconnect path. (role: recent area contributor and refresh author; confidence: high; commits: b29f8fd8e5ca, 286c020748ea, e596e2850d8e; files: ui/src/api/gateway.ts, ui/src/api/gateway.node.test.ts, ui/src/app/gateway-store.ts)
  • ly85206559: Recent gateway auth retry work touched the same client auth/reconnect family and helps route compatibility questions around connect failures. (role: adjacent gateway auth contributor; confidence: medium; commits: b170c08e6d02; files: ui/src/api/gateway.ts, packages/gateway-client/src/client.ts)
  • joshavant: Earlier token fallback and reconnect hardening touched the same connect-failure/auth retry behavior family. (role: adjacent gateway reconnect contributor; confidence: medium; commits: a76e81019333; files: ui/src/api/gateway.ts, ui/src/api/gateway.node.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (4 earlier review cycles)
  • reviewed 2026-07-03T04:19:10.819Z sha 85f920e :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T16:28:24.381Z sha 85f920e :: needs maintainer review before merge. :: none
  • reviewed 2026-07-06T02:33:06.072Z sha c737129 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-06T03:27:05.796Z sha 9137018 :: needs maintainer review before merge. :: none

@ruanrrn
ruanrrn force-pushed the bughunt/fix-control-ui-connect-error-propagation branch from 9c5f1b8 to 65114c9 Compare April 30, 2026 02:53
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels May 19, 2026
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 19, 2026
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels May 19, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

@RomneyDa

Copy link
Copy Markdown
Member

Heads up: this PR needs to be updated against current main before the new required Dependency Guard check can pass.

@ruanrrn
ruanrrn force-pushed the bughunt/fix-control-ui-connect-error-propagation branch from 65114c9 to ad24efb Compare June 1, 2026 09:58
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 1, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 1, 2026
@ruanrrn
ruanrrn force-pushed the bughunt/fix-control-ui-connect-error-propagation branch from b5d05ba to 486ee76 Compare June 1, 2026 13:43
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 1, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 1, 2026
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 15, 2026
@steipete
steipete force-pushed the bughunt/fix-control-ui-connect-error-propagation branch 2 times, most recently from 9137018 to b733441 Compare July 6, 2026 03:46
@steipete
steipete force-pushed the bughunt/fix-control-ui-connect-error-propagation branch from 48fae09 to b29f8fd Compare July 6, 2026 04:10
@openclaw-barnacle openclaw-barnacle Bot removed docs Improvements or additions to documentation gateway Gateway runtime scripts Repository scripts agents Agent runtime and tooling labels Jul 6, 2026
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: XS status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants