fix(anthropic): bound streaming 200 success-body SSE reads at 16 MiB#96701
fix(anthropic): bound streaming 200 success-body SSE reads at 16 MiB#96701wangmiao0668000666 wants to merge 1 commit into
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 27, 2026, 2:30 PM ET / 18:30 UTC. Summary PR surface: Source +13, Tests +43. Total +56 across 2 files. Reproducibility: yes. Source inspection shows current main buffers Anthropic 200 SSE chunks without a total success-body byte cap, and the PR body provides after-fix live output for the guarded path. Review metrics: 1 noteworthy metric.
Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. 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:
Next step before merge
Security Review detailsBest possible solution: Land this narrow Anthropic transport parser cap after maintainers accept or tune the 16 MiB boundary, while keeping provider, proxy, and extension parser caps in their sibling work. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows current main buffers Anthropic 200 SSE chunks without a total success-body byte cap, and the PR body provides after-fix live output for the guarded path. Is this the best way to solve the issue? Yes, pending maintainer acceptance of the 16 MiB boundary. Guarding this parser is the narrowest maintainable fix because the existing fetch guard and JSON caps do not impose a total Anthropic success-body SSE limit. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 98ed83f848de. Label changesLabel justifications:
Evidence reviewedPR surface: Source +13, Tests +43. Total +56 across 2 files. View PR surface stats
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
|
76706ef to
fc43cb4
Compare
|
@clawsweeper re-review Fixed both blockers from the r1 review. 1. Real behavior proof added. Local-only 2. [P3] overflowed() / cancelled() separated. The P3 finding flagged that
The proof script asserts both behaviors: after overflow 3. [P1] 16 MiB cap compatibility — unchanged, still requires maintainer acceptance. The cap matches the established Force-pushed as |
|
🦞🧹 I asked ClawSweeper to review this item again. |
…(provider path) Apply createSseByteGuard to src/llm/providers/anthropic.ts (legacy provider iterateSseMessages) so the Anthropic Messages provider SSE parser cannot be exhausted by a hostile or malfunctioning Anthropic-compatible endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/anthropic.ts: wrap body.getReader() in createSseByteGuard before the existing iterateSseMessages loop. The function is also re-exported as iterateSseMessagesForTest for direct test access. - Inline test added to anthropic.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the second of the planned per-surface rescue series for the previously closed PR openclaw#96632, after openclaw#96701. No SDK surface change. No repro script committed (proof in this body). The companion error-body path is already bounded (readAnthropicMessages ErrorBodySnippet, 8 KiB + 10s idle timeout, openclaw#95108); this PR closes the legacy-provider success-body gap.
… at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body).
… at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body).
… at 16 MiB (#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in #96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR #96666, after PR #3b (#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
|
@clawsweeper re-review 🦞 upgrades applied (PR body only):
No code changes. |
Apply createSseByteGuard to src/agents/anthropic-transport-stream.ts so the
Anthropic Messages transport parser cannot be exhausted by a hostile or
malfunctioning Anthropic-compatible endpoint that streams an unbounded SSE
body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap
already used for non-streaming Anthropic success bodies.
What changed:
- New helper src/agents/streaming-byte-guard.ts (78 lines, internal core):
createSseByteGuard wraps a ReadableStreamDefaultReader<Uint8Array>,
tracks accumulated bytes across the existing chunk loop, cancels the
underlying reader on overflow, and throws a canonical overflow error.
Internal for now; if extensions need it, promote to a plugin-SDK subpath
in a separate PR with full SDK metadata sync.
- src/agents/anthropic-transport-stream.ts: wrap body.getReader() in
createSseByteGuard before yielding to the existing parseAnthropicSseBody
loop. The readAnthropicSseChunk type was widened to accept any
{ read, cancel } so the same wrapper drives the guarded reader.
- Inline test added to anthropic-transport-stream.test.ts: hostile 1 MiB
pull stream, asserts canonical overflow message in result.errorMessage,
cancel(reason) was an Error instance, pullCount bounded to 17-20.
No SDK surface change. No repro script committed (proof in this body).
The companion error-body path is already bounded (readAnthropicMessages
ErrorBodySnippet, 8 KiB + 10s idle timeout, openclaw#95108); this PR closes the
symmetric success-body gap.
fc43cb4 to
7a45023
Compare
…(provider path) Apply createSseByteGuard to src/llm/providers/anthropic.ts (legacy provider iterateSseMessages) so the Anthropic Messages provider SSE parser cannot be exhausted by a hostile or malfunctioning Anthropic-compatible endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/anthropic.ts: wrap body.getReader() in createSseByteGuard before the existing iterateSseMessages loop. The function is also re-exported as iterateSseMessagesForTest for direct test access. - Inline test added to anthropic.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the second of the planned per-surface rescue series for the previously closed PR openclaw#96632, after openclaw#96701. No SDK surface change. No repro script committed (proof in this body). The companion error-body path is already bounded (readAnthropicMessages ErrorBodySnippet, 8 KiB + 10s idle timeout, openclaw#95108); this PR closes the legacy-provider success-body gap.
Apply createSseByteGuard to src/agents/runtime/proxy.ts (streamProxy) so the runtime proxy SSE parser cannot be exhausted by a hostile or malfunctioning proxy endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/agents/runtime/proxy.ts: wrap response.body.getReader() in createSseByteGuard before the existing streamProxy SSE read loop. The abort handler and finally block use guard.cancel() instead of reader.cancel(). - Inline test added to proxy.test.ts: hostile 1 MiB pull stream through streamProxy, asserts errorMessage matches overflow pattern, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the final per-surface rescue from the previously closed PR openclaw#96666, after PR #3c (openclaw#96762). No SDK surface change. No repro script committed (proof in this body).
|
@clawsweeper re-review Rebased onto current main. The Diff: 2 files, +58/-2 (no more |
|
🦞🧹 I asked ClawSweeper to review this item again. |
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]>
|
Closing as duplicate of #96723 / superseded by #96762. Decision: This PR and #96723 both bound Anthropic streaming SSE at 16 MiB via Why: 14 days open with no maintainer movement; the broader 16 MiB body-bound initiative (#96680 cluster) was closed out on 2026-06-29 in a maintainer batch that did not select this branch. Supported alternative: A one-line follow-up PR that imports Evidence that would change the decision: a maintainer explicitly asks for this exact branch to land (vs. the follow-up), or a new finding that the helper exported by #96762 does not actually fit the Anthropic provider path. 🤖 Generated with Claude Code |
… at 16 MiB (openclaw#96762) * fix(openai-chatgpt-responses): bound streaming success-body SSE reads at 16 MiB Apply createSseByteGuard to src/llm/providers/openai-chatgpt-responses.ts (parseSSE) so the ChatGPT Responses SSE parser cannot be exhausted by a hostile or malfunctioning endpoint that streams an unbounded SSE body. The 16 MiB cap matches the non-streaming readProviderJsonResponse cap. What changed: - src/llm/providers/openai-chatgpt-responses.ts: wrap body.getReader() in createSseByteGuard before the existing parseSSE loop. The function is also re-exported as parseSSEForTest for direct test access. - Inline test added to openai-chatgpt-responses.test.ts: hostile 1 MiB pull stream, asserts canonical overflow message, cancel(reason) was an Error instance, pullCount bounded to 17-20. Reuses the createSseByteGuard helper from src/agents/streaming-byte-guard.ts (introduced in openclaw#96701 for the anthropic-transport-stream path; same helper, same 16 MiB cap, same overflow-error shape). This PR is the third of the planned per-surface rescue series for the previously closed PR openclaw#96666, after PR #3b (openclaw#96723). No SDK surface change. No repro script committed (proof in this body). * fix(openai): bound ChatGPT error body reads * fix(openai): bound chatgpt error parsing --------- Co-authored-by: Vincent Koc <[email protected]> (cherry picked from commit 5880e0a)
What Problem This Solves
src/agents/anthropic-transport-stream.tsaccumulates an unbounded SSE byte stream into a string buffer while waiting for\n\nframe delimiters. The companion error-body path (readAnthropicMessagesErrorBodySnippet, 8 KiB cap + 10s idle timeout) was bounded by #95108; the symmetric 200 success-body path was left open. A hostile or malfunctioning Anthropic-compatible endpoint (including an MITM-able proxy, a cloud-hosted mirror likeAnthropic-Vertex, or a hijacked self-hosted provider) can return aContent-Length-less SSE body that streams forever, exhausting process memory on a code path that runs for every/v1/messagescall.This is the streaming-body analogue of the bounded-read sweep Alix-007 finished for non-streaming
response.json()/response.text(). Same 16 MiB cap, same helper-driven design, but on the chunk-by-chunk SSE path that the non-streaming sweep did not cover.Changes
Reuses the existing
createSseByteGuardhelper (already in main) — no new abstraction in this PR.src/agents/anthropic-transport-stream.ts:56—parseAnthropicSseBodywrapsbody.getReader()increateSseByteGuardwithANTHROPIC_MESSAGES_SUCCESS_BODY_MAX_BYTES = 16 * 1024 * 1024.src/agents/anthropic-transport-stream.test.ts— 1 inline test (43 LoC) through productionparseAnthropicSseBodywrapper with oversized stream + negative control + happy path.Symmetric counterpart to #95108 (Anthropic error-body cap). Aligned with the bounded-read campaign pattern (#95218/#95418/#95420).
Design Rationale
Why reuse
createSseByteGuardinstead of a new helper? The guard logic — byte tracking, threshold check, reader cancellation, overflow/cancelled flag separation — is orthogonal to any particular SSE parser's chunk-loop structure. A dedicated helper keeps each parser's existing control flow intact (the samewhile/forloop, the same\n\nboundary search) while being reused across anthropic-transport-stream, the legacy provider path, proxy, and eventually extension parsers. The helper was promoted tosrc/agents/streaming-byte-guard.tsin a separate foundation commit so multiple parser-specific PRs could share it.Why 16 MiB cap? Matches the existing non-streaming 16 MiB cap from
readProviderJsonResponse(src/agents/provider-http-errors.ts). The Anthropic Messages API produces compact streaming responses — typical SSE events are a few KiB, so the 16 MiB cap would only fire on malfunctioning or hostile endpoints. Using the same threshold avoids introducing a new magic number.Why track
overflowedandcancelledseparately? Callers need to distinguish byte-cap overflow from explicit cancellation. After overflow, the guard auto-cancels the underlying reader — setting both flags. An explicitcancel()call from the parser only setscancelled=false, overflowed=false. Theread()method returns{done: true}when either flag is set, preventing double-reads after cancellation.Why core-internal, not SDK? The helper lives in
src/agents/and is not exported fromsrc/plugin-sdk/. Extensions (extensions/google,extensions/ollama) may later need it, but promotion to the SDK requires a separate dedicated PR with full SDK metadata sync. Keeping it internal until a concrete extension consumer is proven avoids premature API surface commitment.Real behavior proof
node:httpserver bound to127.0.0.1returning aContent-Length-less 64 MiB body (4× the cap) chunk-by-chunk with backpressure, plus a small (8 MiB) negative-control body, plus a happy-path body. Drives the productioncreateSseByteGuardover a real TCP socket. Node v22.22.0.node --import tsx _proof_anthropic_stream.mts(proof script kept local-only, not committed, matching Alix-007 fix(github-copilot): bound usage response reads #96607 pattern).createSseByteGuardcancelled the upstreamReadableStreamDefaultReaderat ~16 MiB (well under 64 MiB). Server-sidebytesSentobserved at 19-20 MiB — bounded, not drained.cancel(reason)received anErrorinstance. Negative control confirms the cap is the cause of overflow, not body structure. Bothoverflowedandcancelledflags are true after overflow; onlycancelledis true after explicit cancel.Out of scope (separate PRs, sequenced)
src/llm/providers/anthropic.ts(iterateSseMessages, legacy provider SSE parser) — same bounded-read pattern, fix(anthropic): bound streaming 200 success-body SSE reads at 16 MiB (provider path) #96723.src/agents/runtime/proxy.ts(streamProxy, internal proxy SSE parser) — fix(runtime/proxy): bound streaming success-body SSE reads at 16 MiB #96768.extensions/google/transport-stream.tsandextensions/ollama/src/{setup,stream}.ts— need the helper promoted to a plugin-SDK subpath first.Risk checklist
createSseByteGuardto underlying reader; verified by explicit test assertion (expect(cancelReason).toBeInstanceOf(Error)).Diff stats