Skip to content

fix(gateway): prevent workspace icon failures after startup - #167697

Merged
steipete merged 3 commits into
mainfrom
steipete/perf-micro-http-abort-error-noise-20260919
Oct 9, 2026
Merged

steipete merged 3 commits into
mainfrom
steipete/perf-micro-http-abort-error-noise-20260919

Conversation

@steipete

@steipete steipete commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes workspace icons returning HTTP 500 after successful Gateway startup, with repeated AbortError stacks in the Gateway log.

User Impact

HTTP reads use a live request lifetime. Disconnects cancel cooperating work quietly, shutdown closes unfinished responses, and acknowledged hooks continue under their independent Gateway lifetime. Unexpected errors retain their diagnostics. Operators can observe cancellations through openclaw_gateway_http_cancelled_total{source="client"|"shutdown"}. Normal cancellations stay out of the stability event buffer.

Why This Change Was Made

HTTP listeners inherited the startup AsyncWorkScope after the run loop retired it. Requests now enter the live Gateway owner and get a disconnect-aware child scope. Cancellation and draining restore that request context so abort listeners can schedule cleanup and the Gateway joins it before releasing resources.

The transport owner records successful response completion before Node's normal connection-close handling. This matters on Node 24, which can emit finish with writableFinished=true after a buffered write loses its socket. Normal Connection: close responses and server-selected rejections remain excluded from cancellation counts.

Current UI already deduplicates these image loads. The tested virtual-remount flow issued one metadata request and one image request, retained the image across an actual row unmount/remount, and produced zero failed/cancelled media requests. No UI patch was justified by this evidence.

Non-test/non-doc LOC: +120, using the requested diff filter. The growth supplies the missing cancellation/cleanup lifetime and bounded metric. Existing metric cases move to their current timing owner, and the now-unneeded line-limit suppression is removed. No user configuration, schema, or dependency change.

Evidence

Full public Gateway with synthetic state and a paired-device token; startup retired before the first request:

Measurement Before After
Workspace-icon raw requests 4/4 returned 500 4/4 returned 200
Matching raw-request error records 4 0
Errors per fresh headless browser connection (3 rounds) 1 0
Browser cancellations per connection 0 0

Document, JS, agent-avatar, user-avatar, and assistant-media controls remained 200. The comparison used real routes and public Gateway startup, with no injected route or minimal Gateway. The final rebased candidate also passed all six route families on Node 24, logged zero HTTP errors, and shut down cleanly. The UI proof used current source served by the existing Vite E2E helper and a mocked Gateway; it is not a packaging test.

  • Original-code regression failed with 500 instead of 200 on blacksmith-testbox lease tbx_01m4fcnm2krqcahzgh6qjjm3y4.
  • Pre-rebase proof on tbx_01m4fppr74xnj09jgfva92r0pq: 16 HTTP lifetime cases passed (30.70s cold one-worker wall time), 92 lifecycle/diagnostics/Prometheus cases passed, and 358 Gateway siblings passed with one independently reproduced baseline failure. Export collision and eager-import/PR-wrapper closure guards passed (20 closure tests).
  • Negative controls failed for the intended reasons: retired startup caused 500; ambient abort dispatch lost cleanup ownership; omitting the rejection receipt counted a delivered 413; Node 24 completion-property tracking lost a buffered cancellation.
  • Final integration on blacksmith-testbox lease tbx_01m4ft94fhkbp987fnshcbxz84: exact staged tree verified; core, extension, and scripts lint passed; 123 affected tests passed through node scripts/run-vitest.mjs … --maxWorkers=1 in 53.80s. The full node scripts/check-changed.mjs --staged --base f8e594e461efa21db360d345e986a2d0a543d5a4 gate passed all typechecks and early guards. Its only failure is the dead-export scan; unchanged main source at that exact base reproduces the same four findings. Every remaining check from the owning plan was run separately and passed, including lint, schema, import-cycle, webhook, and pairing guards. The complete integration batch took 39m58s.
  • CI consumer repair on AWS Crabbox lease cbx_a4477a7324ca after Testbox tbx_01m4fy9mykahqexnx71ky785j9 stalled in admission: the old recorder failed with expected 4 to be 3; node scripts/run-vitest.mjs src/logging/diagnostic-stability.test.ts src/logging/diagnostic-stability-bundle.test.ts src/infra/diagnostic-events.test.ts extensions/diagnostics-prometheus/src/service.test.ts --maxWorkers=1 passed 84 tests in 40.98s. Core and logging-test typechecks passed. After simplifying the callback for the executable-line ratchet, the final source passed 37 logging/bundle tests in 33.65s, targeted lint, both size ratchets, formatting, and diff checks. That final run took 4m29s end to end. All leases are stopped.
  • Independent review is clean through P2, including the stability-recorder repair. The rebase preserved both Prometheus tests; follow-ups make existing return values explicit, remove an unused suppression, and exclude metric-only cancellations from stability records.

Known old-base failure: server.hooks-admission.test.ts / returns typed admission failures and leaves the idempotency key retryable returned 502 instead of 200 on both candidate and unchanged 15305ccd53dedd1663a89f92a402480d7b71bbf8. The dead-export findings also reproduce on main base f8e594e461efa21db360d345e986a2d0a543d5a4: bindCliQuestionAnswerAuthority, prepareSessionEventTargetForHost, readSessionEntryCreationTransition, and RESTART_CONTINUATION_CONTEXT_PREFIX. These scans remain failed; no baseline or ignore exception was added.

Historical logs did not include URLs, so the original production route distribution remains unmeasured. The reproduced AbortError originated in server startup retirement; this proof found no reduction in browser aborts. The previous-head CI run (37906971427, head 7441d36e6ea54a0c96e6ef8aec637b6e067b1d44) took 12m51s wall time and 14,012 summed executed-job seconds. All Node tests and all nine UI E2E shards passed; its sole root failure was the newly added event missing from the stability recorder's exhaustive switch. That omission is repaired here and covered by the existing bounded-projection regression. Updated-head CI (37912289175, head ec1d1a4a42202ec639df3db8e404e5e5f4cfdc23) passes the repaired lint shard. It has one independently confirmed inherited type failure: src/cli/update-cli.windows-lifecycle.test.ts(545,11), TS2554 from calling a signal listener without its argument. Scheduled main run 37912611958, job 113761463036, fails identically on the exact tested baseline cff1f4fcb9015c99af0c3b104d88ed13fa08b309; the test, type configuration, package manifest, and lockfile are byte-identical in the PR merge. Main already fixes it in 3223e13a514c6134e10ee7f585627434d245fd20. Final CI completed in 15m38s wall time, including 2m17s queued, and 15,172 summed job seconds: 65 passed, 22 skipped, and only the inherited root plus aggregate failed. All nine UI E2E shards passed. ClawSweeper reviewed the exact final head with zero findings and no pre-merge actions. This PR uses the authorized inherited-CI landing exception; no CI rerun or branch refresh was requested.

Bind HTTP requests and cancellation cleanup to the live Gateway instead of
retired startup work. Preserve acknowledged background hooks and export
bounded client/shutdown cancellation counts without unhandled-error noise.

Observe response completion before transport teardown so Node 24 buffered
disconnects are counted without treating normal connection closes as aborts.
@clawsweeper

clawsweeper Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation gateway Gateway runtime extensions: diagnostics-prometheus size: L labels Oct 9, 2026
@clawsweeper clawsweeper Bot added P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Oct 9, 2026
@clawsweeper

clawsweeper Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

What this changes

The Gateway gives HTTP requests their own live execution lifetime so workspace icons load after startup, disconnected requests cancel cleanly, and accepted background hooks continue.

Example: A user loads a workspace icon after Gateway startup completes.

  • Before: All four recorded icon requests returned HTTP 500.
  • After: All four recorded icon requests returned HTTP 200 without matching HTTP error records.

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) A useful shared-owner repair with convincing production-entry evidence and no identified correctness findings.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (live_output): The recorded public Gateway run exercises the changed HTTP owner after startup retirement with paired-device authentication: icons return 200 instead of 500, sibling resources remain successful, and the Node 24 candidate shuts down cleanly. Recorded transport controls cover disconnect, buffered completion, cleanup ownership, and server rejection accounting; the final diagnostic-only follow-up has focused validation.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Product

Kind: Bug fix · Worth it: Yes · Fix scope: Complete
User problem: Workspace icons fail with HTTP 500 after successful Gateway startup, while repeated AbortError stacks appear in the log.
Reason: The recorded failure violates the existing resource-serving contract. The established Gateway-area owner proposes a shared lifetime repair, with no new user configuration or competing UI implementation.

Merge readiness

✅ Ready for maintainer review

This PR addresses a distinct, source-backed Gateway defect that remains on current main. No actionable patch defect was identified; the latest commit resolves the prior stability-recorder omission.

Priority: P2
Reviewed head: ec1d1a4a42202ec639df3db8e404e5e5f4cfdc23

Before merge

None.

Findings

None.

Agent review details

How this fits together

The Gateway HTTP server receives browser resource requests and hook calls, checks their existing authorization, and dispatches them to resource or agent owners. Request cleanup must finish before the Gateway releases its runtime resources.

flowchart TD
 A[Browser or hook client] --> B[Gateway HTTP server]
 B --> C[Live Gateway execution owner]
 C --> D[Request lifetime and existing authorization]
 D --> E[Resource response]
 D --> F[Accepted independent hook run]
 G[Client disconnect or Gateway shutdown] --> D
 D --> H[Cancellation cleanup and bounded metrics]
Loading

Technical review

Best possible solution:

Keep HTTP execution and cleanup under the existing live Gateway owner while accepted hooks retain their independent lifetime.

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

Yes. The startup-retirement mechanism is clear in source and the recorded public Gateway comparison demonstrates HTTP 500 before the repair and HTTP 200 afterward; this read-only review did not execute target code.

Is this the best way to solve the issue?

Yes. The patch repairs execution custody at the shared HTTP boundary, reuses existing transport and detached-work owners, and leaves resource authorization and stored settings intact.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against 3223e13a514c.

Provenance checked

  • src/gateway/server-http.ts: HTTP connection dispatch keeps the original intent (d7e280f: Serialize application admission and retain transport ownership so body rejection responses reach clients before disposal.)
  • src/gateway/http-request-authority.ts: terminal authority outcomes keeps the original intent (76e32e9: Treat disconnect and authority expiry as settled outcomes, preserve operator denial responses, and retain diagnostics for unexpected errors.)
  • src/gateway/server/hooks.ts: independent hook continuation keeps the original intent (a366218: Keep accepted hook runs tracked and admitted after the originating HTTP request releases admission.)
  • src/infra/http-request-lifecycle.ts: disconnect cancellation keeps the original intent (3a99779: Cancel disconnected request waits without treating ordinary request and response completion as cancellation.)
  • extensions/diagnostics-prometheus/src/service.ts: runtime measurement recording keeps the original intent (feat(diagnostics): retain event-loop delays across health reads #138249: Retain completed event-loop observations cumulatively without changing readiness or lifecycle decisions.)
  • src/infra/diagnostic-events.ts: internal asynchronous runtime measurements keeps the original intent (feat: expose garbage collection duration metrics #139592: Deliver runtime measurements through the existing bounded internal diagnostic queue and exporter gates.)
  • src/logging/diagnostic-stability.ts: subscription filtering keeps the original intent (2456c77: Filter uninterested diagnostics before projection and cloning to avoid repeated hot-path work.)
  • config/max-lines-baseline.txt keeps the original intent (f81f9d8: Maintain a shrink-only baseline for existing oversized-file suppressions.)

Testing

Proof path: shipped entry point.

Security

None.

Evidence

What I checked:

  • Request lifetime repair: The introduced wrapper enters the existing Gateway execution owner, creates a disconnect-aware request scope, restores its context for cancellation and cleanup, and joins outstanding work. (src/gateway/http-request-authority.ts:29, ec1d1a4a4220)
  • Current main still needs the repair: The HTTP server, request-authority module, and connection lifecycle module are unchanged between the introduced base and fetched current main; main lacks the proposed live-request wrapper. (src/gateway/http-request-authority.ts, 3223e13a514c)
  • Recorded production-entry proof: The supplied complete PR body records public Gateway startup with synthetic state and paired-device authentication: workspace-icon requests changed from 4/4 HTTP 500 to 4/4 HTTP 200, matching error records fell from four to zero, and document, JavaScript, avatar, and assistant-media controls remained HTTP 200. The integrated Node 24 candidate passed all six route families and shut down cleanly. Body source identity: d1401876b44c15320bb4801acd7c70128bd75157039f9be38bf040f34bc1eac1. (ec1d1a4a4220)
  • Lifecycle and authorization regression coverage: The shared HTTP server fixture retires startup before serving requests. Cases exercise client disconnect, shutdown with unfinished buffered responses, delivered rejection responses, cleanup ownership, credential expiry, operator revocation, and preservation of unexpected-error diagnostics. (src/gateway/server-http.authority-outcomes.test.ts:34, ec1d1a4a4220)
  • Accepted-hook contract preserved: fix(gateway): keep hook agent runs alive after request completion #104686 established that accepted hooks survive request completion. The existing detached continuation preserves independent root admission and adds its own async lifetime; this PR uses that existing owner rather than creating another admission path. (src/gateway/server/hooks.ts:492, a366218cf46b)
  • Prior review continuity: The delta from the previously reviewed head adds the cancellation exclusion to the stability subscription and exhaustive projection switch, extends the existing bounded-projection regression, and documents the exclusion. The previous structured review listed no findings or rank-up moves. (src/logging/diagnostic-stability.ts:724, ec1d1a4a4220)

Likely related people:

  • Peter Steinberger: Raw commit 76e32e9 adds src/gateway/http-request-authority.ts:104 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: 76e32e924413; files: src/gateway/http-request-authority.ts)
  • Vincent Koc: Raw commit 3a99779 adds src/infra/http-request-lifecycle.ts:40 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: 3a997793133c; files: src/infra/http-request-lifecycle.ts)

PR surface

Source +121, Tests +260, Docs +11, Other -1. Total +391 across 16 files. Added test files: 0.

View PR surface stats
Area Files Added Removed Net
Source 9 190 69 +121
Tests 5 310 50 +260
Docs 1 11 0 +11
Config 0 0 0 0
Generated 0 0 0 0
Other 1 0 1 -1
Total 16 511 120 +391

Review metrics

Metric Value Why it matters
Production versus test growth Production +121 net lines; tests +260 net lines Production growth supplies request cancellation, cleanup ownership, and bounded telemetry; the tests protect distinct transport and lifecycle regressions.

Labels

Label changes:

No label changes.

Label justifications:

  • P2: The demonstrated failure affects workspace-icon loading after startup, with a bounded repair in the shared HTTP lifetime owner.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.
  • proof: sufficient: Contributor real behavior proof is sufficient.

Rating scale

6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior.

Workflow

ClawSweeper edits this one comment on every review. Comment @clawsweeper re-review for a fresh review only; repair and merge need explicit maintainer commands such as @clawsweeper autofix or @clawsweeper automerge.

History

Review history (3 earlier review cycles)
  • reviewed 2026-10-09T07:57:14.595Z sha 9335ed0 :: needs changes before merge. :: none
  • reviewed 2026-10-09T08:52:41.688Z sha 7441d36 :: needs maintainer review before merge. :: none
  • reviewed 2026-10-09T09:08:04.801Z sha 7441d36 :: needs maintainer review before merge. :: none

Reviewed October 9, 2026, 5:51 AM ET / 09:51 UTC (Revision 4).

Keep normal outcomes explicitly undefined and retain the failure outcome
used by cancellation accounting. Remove the obsolete Prometheus line-limit
suppression and its retired baseline entry.
@steipete
steipete marked this pull request as ready for review October 9, 2026 08:45
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Security sensitive changes

This maintainer PR changes sensitive security components.

No secops approval is required. This comment is informational because the PR author has Maintain or Admin access.

  • Current SHA: ec1d1a4a42202ec639df3db8e404e5e5f4cfdc23
  • Maintainer: @steipete
  • Repository role: admin

These security sensitive changes were made:

  • src/gateway/http-request-authority.ts: Controls Gateway authentication, pairing, or caller permissions. Check identity validation, scope checks, and revocation.
  • src/gateway/server-http.ts: Controls Gateway authentication, pairing, or caller permissions. Check identity validation, scope checks, and revocation.

Carefully review these changes before merging.

@steipete
steipete enabled auto-merge (squash) October 9, 2026 08:49
@steipete
steipete marked this pull request as draft October 9, 2026 09:00
auto-merge was automatically disabled October 9, 2026 09:00

Pull request was converted to draft

@clawsweeper clawsweeper Bot added status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Oct 9, 2026
@steipete
steipete marked this pull request as ready for review October 9, 2026 09:35
@steipete
steipete enabled auto-merge (squash) October 9, 2026 09:39
@steipete
steipete disabled auto-merge October 9, 2026 09:55
@steipete

steipete commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Landing with the authorized inherited-CI exception for head ec1d1a4a42202ec639df3db8e404e5e5f4cfdc23.

CI run 37912289175 completed with 65 jobs passed and 22 skipped. Its only root failure is check-test-types-core-1: src/cli/update-cli.windows-lifecycle.test.ts(545,11): TS2554, where listener() omits the required signal argument. The CI gate fails solely downstream of that job. All Node tests and all nine UI E2E shards passed.

Scheduled main run 37912611958, job 113761463036 independently fails identically on the exact tested main baseline cff1f4fcb9015c99af0c3b104d88ed13fa08b309. The affected test, type configuration, package manifest, and lockfile are byte-identical between that baseline and the PR's tested merge. Main already fixes the missing argument in 3223e13.

The final head passed independent review and ClawSweeper with zero findings. Focused remote proof, types, lint, formatting, and ratchets are recorded in the PR body. No CI rerun or branch refresh was used to chase green.

@steipete
steipete merged commit 41589bb into main Oct 9, 2026
211 of 217 checks passed
@steipete
steipete deleted the steipete/perf-micro-http-abort-error-noise-20260919 branch October 9, 2026 09:55
steipete added a commit that referenced this pull request Oct 9, 2026
Bind HTTP request lifetime directly to the completed Gateway kernel instead
of resolving the full route context before request routing. Preserve live
execution tracking, disconnect and shutdown cancellation, and joined cleanup.

Fix the eager lookup introduced by 41589bb (#167697). Keep all existing
cold-route and authority assertions, and wire HTTP fixtures to the same
lifetime input used by production.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Oct 10, 2026
…#167697)

Give HTTP requests a live Gateway-owned, disconnect-aware work scope instead of retaining the retired startup scope. Join request cleanup, keep acknowledged hooks on their detached lifetime, and retain diagnostics for unexpected errors.

Count expected client/shutdown cancellations in Prometheus without filling the stability event buffer. Real Gateway proof changed workspace icons from 4/4 HTTP 500 to 4/4 HTTP 200 and matching error records from 4 to 0.

Focused remote regressions, lint, types, ratchets, and all Node/UI CI tests passed. Land with the verified inherited-CI exception for the Windows signal-listener TS2554 reproduced on main run 37912611958; main already fixes it in 84898cc.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Oct 10, 2026
Bind HTTP request lifetime directly to the completed Gateway kernel instead
of resolving the full route context before request routing. Preserve live
execution tracking, disconnect and shutdown cancellation, and joined cleanup.

Fix the eager lookup introduced by 731adfa (openclaw#167697). Keep all existing
cold-route and authority assertions, and wire HTTP fixtures to the same
lifetime input used by production.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation extensions: diagnostics-prometheus gateway Gateway runtime P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. security-sensitive-changed size: L 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.

1 participant