fix: proxy streams fail fast on oversized or stalled responses#97235
Conversation
|
Codex review: needs changes before merge. Reviewed June 28, 2026, 4:01 PM ET / 20:01 UTC. Summary PR surface: Source +149, Tests +318. Total +467 across 3 files. Reproducibility: yes. from source and PR proof. Current main has fallback-preserving bounded non-OK proxy error coverage, while the PR head rethrows bounded body failures; the PR body also supplies live local HTTP output for configured timeout behavior. Review metrics: 2 noteworthy metrics.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Copy recommended automerge instructionNext step before merge
Security Review findings
Review detailsBest possible solution: Preserve the current non-OK proxy fallback while adding timeout-aware bounded reads, then land one coherent proxy hardening sequence after maintainers accept or tune the runtime limits. Do we have a high-confidence way to reproduce the issue? Yes from source and PR proof. Current main has fallback-preserving bounded non-OK proxy error coverage, while the PR head rethrows bounded body failures; the PR body also supplies live local HTTP output for configured timeout behavior. Is this the best way to solve the issue? No, not as written. The proxy response boundary is the right layer, but the non-OK body catch should preserve current fallback behavior while maintainers decide the fail-closed thresholds and overlapping PR sequence. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against f4201578e50c. Label changesLabel justifications:
Evidence reviewedPR surface: Source +149, Tests +318. Total +467 across 3 files. View PR surface stats
Acceptance criteria:
What I checked:
Likely related people:
What the crustacean ranks mean
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
|
de98718 to
c948133
Compare
c948133 to
359722b
Compare
359722b to
e8bbba9
Compare
…law#97235) * fix proxy response and SSE read bounds * test(line): reuse surrogate regex in action tests --------- Co-authored-by: Vincent Koc <[email protected]>
…law#97235) * fix proxy response and SSE read bounds * test(line): reuse surrogate regex in action tests --------- Co-authored-by: Vincent Koc <[email protected]>
…law#97235) * fix proxy response and SSE read bounds * test(line): reuse surrogate regex in action tests --------- Co-authored-by: Vincent Koc <[email protected]>
…law#97235) * fix proxy response and SSE read bounds * test(line): reuse surrogate regex in action tests --------- Co-authored-by: Vincent Koc <[email protected]>
…law#97235) * fix proxy response and SSE read bounds * test(line): reuse surrogate regex in action tests --------- Co-authored-by: Vincent Koc <[email protected]> (cherry picked from commit cc75783)
Summary
What problem does this PR solve?
streamProxyintroduced a proxy-local idle read guard, but the guard did not consume the existing streamtimeoutMsoption and the proxy request body did not forwardtimeoutMsto the proxy server.Why does this matter now?
What is the intended outcome?
timeoutMsuse that configured window for SSE chunk reads and bounded non-2xx error-body reads.timeoutMsto the proxy server together with the other serializable stream options.What is intentionally out of scope?
/api/streamendpoint contract.What does success look like?
A configured proxied stream can wait beyond 120 seconds when
timeoutMsis set higher, while the default path still fails stalled proxy reads after 120 seconds and oversized response bodies remain bounded.What should reviewers focus on?
streamProxy. The patch fixes that source boundary by forwardingtimeoutMs, resolving it once withresolvePositiveTimerTimeoutMs, and passing the resolved value to both proxy error-body reads and SSE chunk reads.resolvePositiveTimerTimeoutMsremains the canonical positive timer normalization helper;streamProxyowns only proxy response read behavior and does not introduce a new config source, provider-routing rule, schema field, or endpoint contract.streamProxyhardening area; this follow-up does not compete on their byte-cap policy and only repairs the configured idle-timeout compatibility boundary in fix: proxy streams fail fast on oversized or stalled responses #97235.unknown as ReadableStreamDefaultReadercasts are confined to test fixtures that emulate browser reader objects for deterministic pending/cancel behavior.Linked context
Which issue does this close?
Existing PR update for #97235; no separate linked GitHub issue is resolved by this follow-up.
Which issues, PRs, or discussions are related?
streamProxyloop.Was this requested by a maintainer or owner?
No maintainer directly requested this exact follow-up. It addresses the ClawSweeper compatibility finding that a legitimate proxied stream silent for more than 120 seconds would otherwise fail before later content arrives.
Real behavior proof
Behavior or issue addressed:
streamProxynow honors a positive configuredtimeoutMsfor proxy read idleness instead of forcing the 120 second default on every proxied SSE/error-body read.Real environment tested: local OpenClaw PR worktree with the production
streamProxyimplementation and a real local HTTP/api/streamserver that accepts the proxy request and intentionally leaves the SSE response open without bytes.Exact steps or command run after this patch: daily-fix bounded live proof ran a TypeScript harness with
corepack [email protected] exec tsx, plus focused proxy runtime tests and focused agents type checking.Evidence after fix (screenshot, recording, terminal capture, console output, redacted runtime log, linked artifact, or copied live output):
Observed result after fix: the proxy request carried the configured 250ms timeout, and the stalled real HTTP SSE response failed with
Proxy SSE stream stalled: no data received for 250ms, proving the proxy idle guard no longer clamps the read to the old default.What was not tested: no hosted proxy, external provider, Control UI, or full chat flow was used; the proof uses a local HTTP server because this change is at the client-side
streamProxyresponse boundary and needs deterministic stalled-response control.Proof limitations or environment constraints: this follow-up validates the affected proxy boundary directly; maintainer acceptance is still needed for the broader 16 MiB caps and default 120 second fail-closed policy.
Tests and validation
Which commands did you run?
src/agents/runtime/proxy.test.ts.What regression coverage was added or updated?
streamProxycall withtimeoutMs: 180000remains unresolved after 120 seconds, does not cancel the reader early, and then fails at 180 seconds withProxy SSE stream stalled: no data received for 180000ms.timeoutMsand the stalled SSE response fails on the configured value.What failed before this fix, if known?
Proxy SSE stream stalled: no data received for 120000mseven thoughtimeoutMs: 180000was provided; fixing option forwarding and read-timeout resolution addresses the source of that wrong timer value without adding config or changing byte caps.If no test was added, why not?
A focused regression test was added in
src/agents/runtime/proxy.test.ts, and an additional local HTTP proxy-style proof was captured for the configured-timeout behavior.Risk checklist
Did user-visible behavior change? (
Yes/No)Yes, but only for proxied streams that set a positive
timeoutMs: those streams may now wait longer than the previous hard-coded 120 second proxy read idle window.Did config, environment, or migration behavior change? (
Yes/No)No. This uses an existing stream option and does not add config, environment variables, migrations, or new user-facing settings.
Did security, auth, secrets, network, or tool execution behavior change? (
Yes/No)No auth, secrets, or tool execution behavior changed. The network read boundary still fails closed for oversized bodies and stalled reads; the idle duration now follows the existing positive timeout option when present.
What is the highest-risk area?
timeoutMs; the default 120 second behavior and 16 MiB fail-closed caps remain unchanged.How is that risk mitigated?
resolvePositiveTimerTimeoutMshelper, so non-positive or invalid values still fall back to 120 seconds.streamProxycall and passed to both non-2xx error-body reads and SSE chunk reads, keeping behavior consistent within the proxy response boundary.Current review state
What is the next action?
Submit this follow-up through the daily-fix
submit-prflow and let the remote PR checks and labels settle.What is still waiting on author, maintainer, CI, or external proof?
timeoutMsvalues; maintainer acceptance is still needed for the default 120 second idle policy.Which bot or reviewer comments were addressed?
timeoutMs; default 120 second behavior remains unchanged and documented above.