Skip to content

fix(gateway): finish plugin HTTP responses after post-header failures#102125

Merged
steipete merged 3 commits into
openclaw:mainfrom
mushuiyu886:fix/followup-58167
Jul 9, 2026
Merged

fix(gateway): finish plugin HTTP responses after post-header failures#102125
steipete merged 3 commits into
openclaw:mainfrom
mushuiyu886:fix/followup-58167

Conversation

@mushuiyu886

@mushuiyu886 mushuiyu886 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Finish plugin HTTP responses when a route throws after it has already sent headers/body.
  • Keep the existing pre-header failure behavior that returns a plain-text 500 response.
  • Add a real HTTP regression test covering the post-header failure path that used to leave the client body read hanging.
  • Address exact-head check-lint feedback by keeping the regression test lint-clean.

What Problem This Solves

A plugin HTTP route can start a response with writeHead()/write() and then throw. createGatewayPluginRequestHandler caught and logged that error, but when res.headersSent was already true it returned true without ending the response. The gateway had claimed the request as handled, so no outer 404/500 path would close the socket and clients could hang while reading the response body.

User Impact

Plugin endpoints that fail after writing a partial response now complete the HTTP response instead of leaving clients waiting for the body to end. This avoids a gateway availability failure mode for plugin routes without changing the response body that was already sent by the plugin.

  • Why it matters / User impact: a plugin route failure should not leave HTTP clients blocked after the gateway has already claimed the request as handled. This patch preserves the plugin's already-sent status/body and fixes the missing response lifecycle close.

Origin / follow-up

Follows #58167.

  • Gap this fills: the plugin HTTP route error path handled failures before headers were sent, but did not close an already-started response after a post-header route failure.
  • Consistency: this keeps the existing route-dispatch contract that a thrown route is logged and treated as handled, while ensuring the claimed response is also finished.
  • Closing issue: N/A — this is a focused follow-up to the gateway/plugin HTTP route failure-handling surface.

Competition / linked PR analysis

No direct open issue or open PR competition was found for createGatewayPluginRequestHandler, headersSent, or plugin HTTP post-header route failures. The change is limited to the gateway plugin HTTP route catch path and one regression test for that path.

Related open PR scan: searched the plugin HTTP handler surface and headersSent failure path; no broader canonical PR or same-point open fix was found.

Real behavior proof

  • Behavior or issue addressed: Plugin HTTP route responses no longer hang when the route writes headers/body and then throws.
  • Canonical reachability path: user HTTP input/request to a registered plugin route → PluginHttpRouteRegistration type and plugin registry ingestion → gateway runtime request/ServerResponse in createGatewayPluginRequestHandler → handler sends headers/body and throws → HTTP response effect now completes instead of hanging.
  • Boundary crossed: local-runtime; a real Node HTTP server accepted a TCP request, dispatched through createGatewayPluginRequestHandler, and a real fetch() client read the response body to completion.
  • Shared helper / provider constraint check: N/A — this does not touch provider parsing, shared normalization helpers, config schema, or third-party provider constraints.
  • Architecture / source-of-truth check: createGatewayPluginRequestHandler is the gateway source-of-truth boundary for claiming matched plugin HTTP route requests and handling thrown route failures; this does not touch schema, provider routing, default values, or migrations.
  • What did NOT change: route matching, auth, runtime scopes, plugin registration, pre-header 500s, successful route behavior, and upgrades remain unchanged; only the post-header thrown-route response lifecycle is changed.
  • Target test file: src/gateway/server/plugins-http.test.ts.
  • Scenario locked in: a registered plugin route writes status/body through a real ServerResponse, throws, and the client must be able to finish response.text() without aborting.
  • Why this is the smallest reliable guardrail: the regression uses a real Node HTTP server instead of the existing mock response helper because the bug is the socket/body completion behavior after headers are sent.
  • Patch quality notes: one production branch closes an already-started response when it is not already ended; one regression test exercises the failing lifecycle path through the gateway handler. The follow-up commit only adjusts the regression test wrapper to satisfy exact-head lint rules.
  • Maintainer-ready confidence: high — the root cause is a missing res.end() in the exact catch path that claims the route as handled, with focused before/after coverage and no unrelated cleanup.
  • Real environment tested: Linux local runtime in the OpenClaw PR worktree, Node HTTP server/client, current patch in /media/vdb/code/ai/aispace/openclaw-worktrees/pr-102125.
  • Exact steps or command run after this patch:
node scripts/test-projects.mjs src/gateway/server/plugins-http.test.ts
pnpm exec oxlint src/gateway/server/plugins-http.test.ts
  • Evidence after fix:

Focused regression suite on current PR head:

$ node scripts/test-projects.mjs src/gateway/server/plugins-http.test.ts
[test] starting test/vitest/vitest.gateway.config.ts

 RUN  v4.1.9 /media/vdb/code/ai/aispace/openclaw-worktrees/pr-102125

 Test Files  4 passed (4)
      Tests  84 passed (84)
[test] passed 1 Vitest shard in 101.36s

Focused lint proof for the exact file that failed check-lint:

$ pnpm exec oxlint src/gateway/server/plugins-http.test.ts
focused oxlint passed for src/gateway/server/plugins-http.test.ts

Live HTTP completion proof from the same route behavior:

{
  "status": 200,
  "body": "partial",
  "warnings": [
    "plugin http route failed (demo): Error: boom"
  ],
  "completedWithoutAbort": true
}
  • Observed result after fix: The client received status 200, read body partial, observed the expected warning log, and completed without the abort/timeout path. The regression test remains passing after the lint-only test wrapper adjustment, and the modified test file now passes focused oxlint.
  • What was not tested: No third-party plugin package, external provider, browser UI, or deployed gateway was used; this path is local gateway HTTP route dispatch and does not require an external service boundary. A full local pnpm lint --threads=8 attempt was terminated by local tsgolint SIGTERM before producing rule violations, so the exact CI-reported file was verified with focused oxlint instead.
  • Fix classification: Root cause fix.

Review findings addressed

  • RF-001: status: ⏳ waiting on author is addressed by the follow-up commit and refreshed proof for the exact lint/test findings below.
  • RF-002: fixed the two lint errors in src/gateway/server/plugins-http.test.ts without changing the production fix or regression scenario.
  • RF-003: reran the focused plugin HTTP test after the test-shape repair; local full pnpm lint --threads=8 was attempted but tsgolint received SIGTERM before reporting rule violations, so the exact changed file was verified with focused oxlint and remote exact-head CI remains the full-lint authority.
  • RF-004: repaired exact-head lint on the new regression test by changing only the test wrapper shape.
  • RF-005: kept the repair narrow: no production code changes, no product-direction changes, and no unrelated cleanup.
  • RF-006: wrapped the createServer request listener so the listener returns void while still awaiting the handler inside a voided async IIFE.
  • RF-007: changed the server.close Promise executor to a block body so it does not return the server.close() result.
  • RF-008: attempted pnpm lint --threads=8; local tsgolint terminated with SIGTERM before rule output, and the exact CI-reported file now passes pnpm exec oxlint src/gateway/server/plugins-http.test.ts.
  • RF-009: reran pnpm test src/gateway/server/plugins-http.test.ts; the gateway shard passed with 4 files and 84 tests.

Regression Test Plan

  • pnpm test src/gateway/server/plugins-http.test.ts
  • pnpm exec oxlint src/gateway/server/plugins-http.test.ts
  • Real HTTP proof using a route that writes 200 + partial, throws, and verifies fetch().text() completes without aborting.

Merge risk

  • Risk labels considered: availability, compatibility.
  • Risk explanation: the bug is an availability hang in plugin HTTP error handling; compatibility risk is low because the patch does not rewrite plugin route contracts or alter successful route behavior.
  • Why acceptable: the new branch only runs after headers were already sent and the response has not already ended, so it closes the claimed response without replacing the plugin's emitted status or body.

Risk / Compatibility

Low. The change only affects the error path after a plugin route has already sent headers. The pre-header thrown-route behavior still returns 500 Internal Server Error, route matching/auth behavior is unchanged, and plugin upgrade handling is unchanged.

What was not changed

  • No changes to route matching, gateway auth checks, runtime scope construction, or plugin upgrade handling.
  • No changes to plugin route registration APIs.
  • No changes to the body/status already emitted by a plugin before it throws.

Scope boundary: the patch is intentionally limited to response finalization after a post-header route exception and does not change plugin route selection, permissions, protocol, or public registration API behavior.

Root Cause

  • Root cause: Response lifecycle root cause: the catch path in createGatewayPluginRequestHandler only ended the response when headers had not been sent. Because the handler returned true after a plugin had already sent headers/body, the gateway claimed the request while leaving ServerResponse open.
  • Why this is root-cause fix: Ending the response when headersSent is true and writableEnded is false closes the exact lifecycle gap that caused the client hang, while preserving the existing 500 handling for failures that happen before any response is started.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S labels Jul 8, 2026
@clawsweeper

clawsweeper Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 9, 2026, 3:56 AM ET / 07:56 UTC.

Summary
The branch updates the gateway plugin HTTP route catch path to end already-started, not-destroyed responses after route exceptions and adds focused regression tests for completed and destroyed responses.

PR surface: Source +2, Tests +96. Total +98 across 2 files.

Reproducibility: yes. Source inspection shows current main claims the plugin route after a post-header throw without ending the response, and the PR adds a real HTTP regression path that exercises the hanging client read.

Review metrics: none identified.

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

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

Rank-up moves:

  • none.

Risk before merge

  • [P1] The branch is mergeable but behind current main, so maintainers should rely on refreshed exact-merge or merge-queue checks before landing.

Maintainer options:

  1. Decide the mitigation before merge
    Land the narrow response-finalization fix after refreshed merge gating, preserving plugin route API behavior, route matching, auth, and pre-header 500 responses.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] No repair lane is needed because review findings are empty and the remaining action is ordinary maintainer landing with refreshed merge checks.

Security
Cleared: The diff only changes a gateway plugin HTTP response lifecycle branch and focused tests, with no dependency, secret, permission, CI, package, or auth-surface change.

Review details

Best possible solution:

Land the narrow response-finalization fix after refreshed merge gating, preserving plugin route API behavior, route matching, auth, and pre-header 500 responses.

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

Yes. Source inspection shows current main claims the plugin route after a post-header throw without ending the response, and the PR adds a real HTTP regression path that exercises the hanging client read.

Is this the best way to solve the issue?

Yes. Ending only an already-started, not-ended, not-destroyed response in createGatewayPluginRequestHandler is the narrow owner-boundary fix and avoids changing plugin route contracts or pre-header error behavior.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 18b24edea627.

Label changes

Label justifications:

  • P2: This is a focused gateway availability bugfix for plugin HTTP error handling with limited blast radius and no evidence of a broad live outage.
  • 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. Sufficient (live_output): The PR body includes focused test/lint output plus copied live HTTP output from a real Node server/client showing status 200, body partial, the expected warning, and completion without abort.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes focused test/lint output plus copied live HTTP output from a real Node server/client showing status 200, body partial, the expected warning, and completion without abort.
Evidence reviewed

PR surface:

Source +2, Tests +96. Total +98 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 2 0 +2
Tests 1 97 1 +96
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 99 1 +98

What I checked:

  • Repository policy read: Root AGENTS.md plus the scoped gateway and plugins AGENTS.md files were read fully; their gateway hot-path and plugin-boundary guidance drove the caller/test/history checks rather than a diff-only review. (AGENTS.md:13, 18b24edea627)
  • Current main gap: On current main the plugin route catch path logs the exception, writes a 500 only before headers are sent, then returns true without closing a response whose headers/body were already started. (src/gateway/server/plugins-http.ts:198, 18b24edea627)
  • Caller ownership boundary: Gateway request stages stop when a stage returns true, and the plugin-http stage delegates directly to handlePluginRequest, so a claimed plugin route owns completing its ServerResponse before later stages are skipped. (src/gateway/server-http.ts:331, 18b24edea627)
  • PR fix on head: At the PR head, the catch path preserves the pre-header 500 behavior and adds an else branch that calls res.end() only when the response is not writableEnded and not destroyed. (src/gateway/server/plugins-http.ts:204, 4ea595c1ad0b)
  • Regression coverage on head: The new test starts a real Node HTTP server, has a plugin route write status/body then throw, and asserts fetch().text() completes with the partial body instead of timing out. (src/gateway/server/plugins-http.test.ts:454, 4ea595c1ad0b)
  • Destroyed-response guard on head: A second added test covers the maintainer follow-up commit: when a plugin has already destroyed a headers-sent response before throwing, the handler claims the route but does not call end again. (src/gateway/server/plugins-http.test.ts:523, 4ea595c1ad0b)

Likely related people:

  • steipete: Authored the latest PR head commit adding the destroyed-response guard, is assigned on the live PR, and previously merged plugin HTTP route registry work touching the same handler and tests. (role: recent area contributor and assigned reviewer; confidence: high; commits: 4ea595c1ad0b, a69f6190ab56; files: src/gateway/server/plugins-http.ts, src/gateway/server/plugins-http.test.ts, src/gateway/server-runtime-state.ts)
  • vincentkoc: Merged the related plugin HTTP runtime-scope work in fix(gateway): narrow plugin route runtime scopes #58167, including the same handler and adjacent failure-path tests this PR follows up on. (role: recent area contributor; confidence: high; commits: 2a1db0c0f1fa; files: src/gateway/server/plugins-http.ts, src/gateway/server/plugins-http.test.ts, src/gateway/server/plugins-http.runtime-scopes.test.ts)
  • Sid-Qin: Merged earlier plugin HTTP route ordering work that made plugin routes own responses before Control UI fallback, which is part of the routing boundary reviewed here. (role: adjacent contributor; confidence: low; commits: 41c8734afdb4; files: src/gateway/server-http.ts, src/gateway/server.plugin-http-auth.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-08T09:05:49.544Z sha f6a225e :: needs maintainer review before merge. :: none
  • reviewed 2026-07-08T09:19:28.881Z sha f6a225e :: needs changes before merge. :: [P2] Wrap the request listener so it returns void | [P2] Use a block body for the close promise executor
  • reviewed 2026-07-08T09:34:41.545Z sha fb2cd29 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-08T11:43:49.506Z sha fb2cd29 :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. labels Jul 8, 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. 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: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. 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. labels Jul 8, 2026
@steipete steipete self-assigned this Jul 9, 2026
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Maintainer proof complete on exact head 4ea595c1ad0b9f533233510468ea9f6994fd3c71.

I tightened the catch-path cleanup so a route that already destroyed the response is left alone, while a route that wrote headers/body and then threw is explicitly ended. This matches Node's response lifecycle: end() completes a response, but a destroyed stream should not be ended again.

Proof:

Known gap: no external plugin server was needed; the regression uses the real Node HTTP server and exercises both post-header throw and destroyed-response paths directly.

Node contracts checked: https://nodejs.org/download/release/latest-v24.x/docs/api/http.html and https://nodejs.org/api/stream.html

@steipete
steipete merged commit 240d350 into openclaw:main Jul 9, 2026
99 checks passed
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 10, 2026
…openclaw#102125)

* fix(gateway): finish plugin HTTP responses after post-header failures

* test(gateway): satisfy plugin HTTP regression lint

* fix(gateway): skip ending destroyed plugin responses

---------

Co-authored-by: Peter Steinberger <[email protected]>
RomneyDa added a commit that referenced this pull request Jul 22, 2026
* fix: gate diagnostics command to owners

(cherry picked from commit 170bf72)

* fix(agent): replace self-wait with deferred release in retained-lock abort cleanup (#96100)

* fix(agent): wait for retained session write before releasing held lock on abort

* fix(agent): replace self-wait with deferred release in retained-lock abort cleanup

* fix(test): reject fallback acquire with SessionWriteLockTimeoutError in active-scope cleanup test

* fix(agent): trim retained-lock comments

Signed-off-by: sallyom <[email protected]>

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: sallyom <[email protected]>
(cherry picked from commit 0a042f6)

* fix(gateway): resume channel after pending task recovery

(cherry picked from commit 6039da3)

* fix(gateway): resume channel after pending task recovery

(cherry picked from commit ecd29fe)

* fix(outbound): ignore empty delivery receipts (#79811)

(cherry picked from commit 9a735be)

* fix(agents): guard delivery-evidence attachment recursion against cycles (#97041)

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

---------

Co-authored-by: Pick-cat <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit 4985671)

* fix(opencode-go): re-arm idle timer on block-boundary events to prevent false stalled-stream abort (#97128)

* fix(opencode-go): re-arm idle timer on block-boundary events to prevent false stalled-stream abort

When the opencode-go model finalizes a tool call and deliberates before
the next one, the provider emits real block-boundary SSE events
(text_end, thinking_end, toolcall_start, toolcall_end) that prove the
socket is alive, but the watchdog's isProviderProgressEvent only
returned true for token deltas (text_delta, thinking_delta,
toolcall_delta). This caused the idle timer to fire and falsely abort a
live stream, replacing a completed answer with a stalled error and
dropping the provider's real done event.

Fix: include block-boundary events in isProviderProgressEvent so the
idle timer is re-armed on any forward-progress provider event.
text_start and thinking_start are intentionally excluded because they
are synthetic preamble events that should not shorten the first-event
window.

Closes #96518

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* test(opencode-go): satisfy lint in stream regression

* test(opencode-go): satisfy lint in stream regression

* test(opencode-go): satisfy lint in stream regression

---------

Co-authored-by: Claude Opus 4.8 <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit 552ec2b)

* fix(model-fallback): don't rethrow provider-side AbortErrors as user cancellations (#90908)

* fix(model-fallback): don't rethrow provider-side AbortErrors as user cancellations

When the LLM API closes the connection mid-stream, the fetch layer
surfaces AbortError("This operation was aborted") with no external
abort signal triggered. The old guard `shouldRethrowAbort()` returned
false for these errors (because isTimeoutError matched the message),
so they fell through to the fallback loop but were never retried —
the error propagated up and produced SILENT_REPLY_TOKEN in group
sessions, permanently silencing the topic.

Replace the guard with a direct check: only rethrow AbortError when
the external abort signal is actually set (user/gateway cancellation).
Provider-side AbortErrors without an external signal now fall through
to the next fallback candidate, giving the system a chance to recover.

* fix(cron): forward abort signal into runWithModelFallback

Thread the cron executor's abort signal into the shared
runWithModelFallback call so that cron timeouts and cancellations
stop the fallback chain instead of retrying with the next candidate.

Previously, the run callback checked params.abortSignal?.aborted and
threw, but runWithModelFallback itself had no signal — so the new
guard in model-fallback.ts could not distinguish a caller abort from
a provider-side AbortError and would retry silently.

Also adds a focused regression test verifying the signal is forwarded.

---------

Co-authored-by: Shengting Xie <[email protected]>
Co-authored-by: yayu <[email protected]>
(cherry picked from commit 98ed83f)

* fix(browser): block node routes when sandbox host control is disabled (#97958)

(cherry picked from commit 2cf765f)

* fix(exec): bind Windows allowlist execution path (#98260)

* fix(exec): bind windows allowlist execution path

* fix(exec): add windows shadow execution proof

* fix(exec): preserve wildcard allowlist behavior

* fix(exec): correct blocked plan test fixture

(cherry picked from commit 3811001)

* fix(mcp): suppress unhandled error on stderr pipe in stdio transport (#99803)

* fix(mcp): suppress unhandled error on stderr pipe in stdio transport

When child.stderr is piped to stderrStream without an error
handler, a stream-level error (EPIPE, I/O failure) crashes the
process. Add a noop error handler before the pipe, consistent
with the error handlers already present on stdin and stdout.

Co-Authored-By: Claude <[email protected]>

* test(mcp): add regression test for stderr pipe error suppression

Co-Authored-By: Claude <[email protected]>

* fix(mcp): report stderr stream errors

* fix(mcp): report stderr stream errors

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit 1b84316)

* Harden macOS SQLite WAL checkpoints (#99067)

(cherry picked from commit f7f1be2)

* fix(secrets): suppress unhandled stdout/stderr stream errors in exec resolver (#100521)

* fix(secrets): suppress unhandled stdout/stderr stream errors in exec resolver

* proof(secrets): add real behavior proof script for exec resolver stream error catch

* proof(secrets): replace wrapper with real exec resolver stream error proof

* style: apply oxfmt to changed files

(cherry picked from commit c9a0783)

* fix(agents): retry transient filesystem races when reading workspace bootstrap files (#100910)

* fix(agents): retry transient filesystem races when reading workspace bootstrap files

* fix(agents): retry transient boundary resolution

---------

Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit f36d170)

* fix(gateway): finish plugin HTTP responses after post-header failures (#102125)

* fix(gateway): finish plugin HTTP responses after post-header failures

* test(gateway): satisfy plugin HTTP regression lint

* fix(gateway): skip ending destroyed plugin responses

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 240d350)

* fix(gateway): validate exact custom browser origins (#38290)

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit fa0349a)

* fix: block unspecified trusted DNS targets (#103075)

(cherry picked from commit c70f3d0)

* fix(channels): make nack callbacks idempotent (#104919)

* fix(channels): make nack callbacks idempotent

* fix(channels): coalesce overlapping nack callbacks

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 02d307e)

* fix(channels): prevent base URL credentials in status output (#107754)

* fix(channels): redact credentials in account URLs

* fix(channels): sanitize final status summaries

(cherry picked from commit 210340f)

* fix(channels): prevent lifecycle listener buildup (#109108)

(cherry picked from commit 0e1fad7)

* fix(sandbox): use Buffer.byteLength for env var value size limit (#105017)

* fix(sandbox): use Buffer.byteLength for env var value size limit

validateEnvVarValue checked value.length (UTF-16 code units) against
the 32768-byte limit, so multi-byte CJK values like "值".repeat(11000)
passed the check despite exceeding 33 KB in UTF-8. Switch to
Buffer.byteLength(value, "utf8") so the limit matches the actual byte
count the OS and child processes see.

* test(sandbox): simplify env byte-limit coverage

Co-authored-by: 唐梓夷0668001293 <[email protected]>

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 84fb48c)

* fix(gateway): guard process.kill ESRCH race in signalVerifiedGatewayPidSync (#109590)

* fix(gateway): guard process.kill ESRCH race in signalVerifiedGatewayPidSync

A verified gateway process can exit between the argv validation check and
the process.kill call, causing an unhandled ESRCH error. Wrap the kill in
try-catch and silently swallow ESRCH (process already gone = signal
already delivered).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* docs(gateway): explain ESRCH signal race

Co-authored-by: 丁宇婷0668001435 <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 853b1a8)

* fix(litellm): guard loopback hostname auto-allow with isIP to prevent DNS SSRF bypass (#110693)

* fix(litellm): guard loopback hostname auto-allow with isIP to prevent DNS bypass

The isAutoAllowedLitellmHostname helper auto-enables private-network access
for loopback-style hosts. Before this fix, lowered.startsWith("127.")
matched DNS hostnames like 127.evil.com, letting remote endpoints bypass
the explicit allowPrivateNetwork opt-in — a SSRF risk.

Add isIP(host)===4 guard so only literal IPv4 loopback addresses qualify.
Same canonical pattern as extensions/slack/src/monitor/relay-source.ts:271
and the codex loopback fix.

Co-Authored-By: Claude <[email protected]>

* test(litellm): cover loopback endpoint policy

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 3d03b60)

* fix(discord): sustained gateway bursts stop growing memory (#110954)

* fix(discord): sustained gateway bursts stop growing memory

* fix(discord): contain gateway queue overflow

* fix(discord): drop oldest saturated gateway sends

Co-authored-by: 张贵萍0668001030 <[email protected]>

* fix(discord): surface gateway overflow warnings

Co-authored-by: 张贵萍0668001030 <[email protected]>

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 69aeba9)

* fix(gateway): bound busy channel health by real run age (#103793)

* fix(gateway): bound busy channel health by real run age

The channel health policy treats a channel as healthy-busy even while
disconnected, bounded only by a 25 minute stale ceiling measured from
lastRunActivityAt. The run-state heartbeat refreshes lastRunActivityAt
every 60 seconds for as long as any run is active, so a run that hangs
forever (for example a send blocking on a dead socket after the
transport already reported connected:false) keeps that timestamp fresh
and the stuck ceiling is never reached. The account is then reported
healthy forever by the health monitor, readiness probe, and health CLI,
and no restart ever fires.

createRunStateMachine now tracks each in-flight run's start time keyed by
an opaque run handle and publishes the oldest still-active run's start as
activeRunStartedAt. The health policy busy override keys its ceiling off
the real run age, so a run stuck longer than the threshold reports stuck
and the monitor can restart it. Because the reported start is the oldest
active run and advances to the next-oldest as runs complete, a channel
churning through many short overlapping runs (activeRuns above 1 across
concurrent queue keys) stays healthy; only a genuinely hung run breaches
the ceiling. Short and active runs stay healthy and the existing
lastRunActivityAt fallback is preserved for snapshots without a start
time.

* fix(channels): retain run-state callback compatibility

Keep the released zero-argument onRunEnd callback source-compatible while allowing internal queue callers to pass a run handle for exact concurrent-run accounting. The compatibility path closes the oldest active run, preserving existing lifecycle behavior for consumers that do not use handles.

* fix(channels): keep anonymous runs out of age tracking

The zero-argument lifecycle callbacks cannot identify which concurrent run completed, so they must not update the identity-sensitive run start used by channel health. Keep their busy count separately and reserve exact start tracking for the shared queue's handle-aware lifecycle path.

* fix(channels): keep tracked runs internal

Keep the public run-state lifecycle callbacks unchanged. The channel queue now owns opaque run identity and augments its status updates with the oldest active queue run, so implementation details do not expand the SDK surface.

* fix(channels): type queue run start status

Keep activeRunStartedAt in the internal status patch type so the queue can publish its private tracked-run age through the existing status sink.

* fix(channels): wrap isActive to satisfy unbound-method lint

* fix(gateway): gate busy run-age ceiling on disconnected transport

(cherry picked from commit 18b79d9)

* fix(deps): update fast-uri past advisory

(cherry picked from commit 1be9db0)

* fix(release): adapt maintenance-line hardening

Backport/adapt 18ec9ce, dea1fe1, 7f32b6c, 1da345e, 931ac3e, 89780d5, and c0d99ed for the 2026.6 extended-stable maintenance line.

* fix(deps): bump protobufjs to 7.6.5

Backport-adapted from a230f74.

* test(gateway): cover bounded macOS process probe

* chore(release): prepare 2026.6.34

* test(dotenv): share path override environment assertions

* fix(release): resolve 2026.6.34 CI blockers

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: joshavant <[email protected]>
Co-authored-by: Peter Lee <[email protected]>
Co-authored-by: sallyom <[email protected]>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: Liu Wenyu <[email protected]>
Co-authored-by: pick-cat <[email protected]>
Co-authored-by: Pick-cat <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
Co-authored-by: weiqinl <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
Co-authored-by: shengting <[email protected]>
Co-authored-by: Shengting Xie <[email protected]>
Co-authored-by: yayu <[email protected]>
Co-authored-by: Agustin Rivera <[email protected]>
Co-authored-by: cxbAsDev <[email protected]>
Co-authored-by: ooiuuii <[email protected]>
Co-authored-by: Masato Hoshino <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
Co-authored-by: mushuiyu886 <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
Co-authored-by: Bruno Wowk (Volky) <[email protected]>
Co-authored-by: Pavan Kumar Gondhi <[email protected]>
Co-authored-by: Glucksberg <[email protected]>
Co-authored-by: xingzhou <[email protected]>
Co-authored-by: tzy-17 <[email protected]>
Co-authored-by: krissding <[email protected]>
Co-authored-by: lsr911 <[email protected]>
Co-authored-by: Yuval Dinodia <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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. size: S 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.

2 participants