Skip to content

fix(anthropic): bound streaming 200 success-body SSE reads at 16 MiB#96701

Closed
wangmiao0668000666 wants to merge 1 commit into
openclaw:mainfrom
wangmiao0668000666:fix/anthropic-transport-stream-bound
Closed

fix(anthropic): bound streaming 200 success-body SSE reads at 16 MiB#96701
wangmiao0668000666 wants to merge 1 commit into
openclaw:mainfrom
wangmiao0668000666:fix/anthropic-transport-stream-bound

Conversation

@wangmiao0668000666

@wangmiao0668000666 wangmiao0668000666 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

src/agents/anthropic-transport-stream.ts accumulates an unbounded SSE byte stream into a string buffer while waiting for \n\n frame 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 like Anthropic-Vertex, or a hijacked self-hosted provider) can return a Content-Length-less SSE body that streams forever, exhausting process memory on a code path that runs for every /v1/messages call.

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 createSseByteGuard helper (already in main) — no new abstraction in this PR.

  • src/agents/anthropic-transport-stream.ts:56parseAnthropicSseBody wraps body.getReader() in createSseByteGuard with ANTHROPIC_MESSAGES_SUCCESS_BODY_MAX_BYTES = 16 * 1024 * 1024.
  • src/agents/anthropic-transport-stream.test.ts — 1 inline test (43 LoC) through production parseAnthropicSseBody wrapper 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 createSseByteGuard instead 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 same while/for loop, the same \n\n boundary search) while being reused across anthropic-transport-stream, the legacy provider path, proxy, and eventually extension parsers. The helper was promoted to src/agents/streaming-byte-guard.ts in 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 overflowed and cancelled separately? Callers need to distinguish byte-cap overflow from explicit cancellation. After overflow, the guard auto-cancels the underlying reader — setting both flags. An explicit cancel() call from the parser only sets cancelled=false, overflowed=false. The read() 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 from src/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

  • Behavior addressed: unbounded buffering of the Anthropic Messages streaming 200 success-body SSE read; after the fix the read is capped at 16 MiB and the stream is cancelled on overflow.
  • Real environment tested: a real node:http server bound to 127.0.0.1 returning a Content-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 production createSseByteGuard over a real TCP socket. Node v22.22.0.
  • Exact steps: 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).
  • Evidence after fix:
    === Anthropic streaming success-body bounded read (cap=16777216, would-stream≈67108864) ===
    
    PASS  Anthropic streaming success body bounded: rejected with "Anthropic Messages success body exceeded 16777216 bytes (received 16836692)..."; bytesSent=20971520 (< 67108864); server.aborted=true; overflowed=true; cancelled=true
    PASS  cap-trace: bounded reader cancelled at 16756082 bytes (full body = 67108864); server.bytesSent=19922944; server.aborted=true
    PASS  negative control: small body fully drained (8388608 bytes >= 8388608); cap did not trigger; overflowed=false; cancelled=false; aborted=false
    PASS  happy path: small valid Anthropic SSE response drained end-to-end (88 bytes <= 16777216)
    
    === All Anthropic streaming success-body bounded-read assertions passed ===
    
  • Observed result after fix: createSseByteGuard cancelled the upstream ReadableStreamDefaultReader at ~16 MiB (well under 64 MiB). Server-side bytesSent observed at 19-20 MiB — bounded, not drained. cancel(reason) received an Error instance. Negative control confirms the cap is the cause of overflow, not body structure. Both overflowed and cancelled flags are true after overflow; only cancelled is true after explicit cancel.
  • What was not tested: live Anthropic API call (the proof exercises the same production helper against the same shape of attack the live endpoint could carry); cross-platform Node 18/20 differences (Node 22 only, matches CI).

Out of scope (separate PRs, sequenced)

Risk checklist

  • User-visible behavior change? No — normal-sized responses unaffected; oversized responses now throw a clear overflow error instead of silently accumulating memory.
  • Config/env/migration change? No.
  • Security/auth/secrets/network/tool-execution change? Yes — OOM/DoS protection for Anthropic Messages streaming success body (16 MiB cap + reader cancellation on overflow).
  • Highest-risk area: cancel propagation from createSseByteGuard to underlying reader; verified by explicit test assertion (expect(cancelReason).toBeInstanceOf(Error)).

Diff stats

2 files changed, 58 insertions(+), 2 deletions(-)

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jun 25, 2026
@clawsweeper

clawsweeper Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 27, 2026, 2:30 PM ET / 18:30 UTC.

Summary
The branch imports createSseByteGuard, adds a 16 MiB Anthropic Messages success-body cap around parseAnthropicSseBody, widens the reader type, and adds an oversized-stream regression test.

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.

  • Anthropic SSE Success-Body Cap: 1 added at 16 MiB. This is the compatibility boundary maintainers should notice because oversized streaming success responses now fail closed instead of continuing.

Root-cause cluster
Relationship: canonical
Canonical: #96701
Summary: This PR is the canonical open fix for the Anthropic Messages transport parser; related PRs cover sibling parsers or earlier superseded broad attempts.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

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:

  • [P2] Maintainers should accept the 16 MiB cap or request a tuned limit before merge.

Risk before merge

  • [P1] Existing Anthropic-compatible streaming success responses above 16 MiB will now throw and cancel instead of continuing, so maintainers should explicitly accept that fail-closed boundary or request a tuned cap before merge.

Maintainer options:

  1. Accept The 16 MiB Anthropic Cap (recommended)
    Maintainers can accept the fail-closed behavior because it matches existing provider success-body caps and prevents unbounded SSE memory growth.
  2. Tune The Limit Before Merge
    If legitimate Anthropic-compatible streams can exceed 16 MiB, adjust the constant and refresh the overflow plus happy-path proof before landing.
  3. Pause For One Streaming Policy
    If maintainers want one cross-parser SSE size policy first, pause this PR and settle the broader cap design across the sibling streaming surfaces.

Next step before merge

  • [P1] Needs maintainer acceptance or tuning of the 16 MiB fail-closed compatibility boundary; there is no narrow automated repair pending.

Security
Cleared: The diff hardens an availability-sensitive provider stream path and does not add dependencies, lockfile changes, CI permissions, secret handling, package metadata, or third-party execution.

Review details

Best 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 changes

Label justifications:

  • P2: This is normal-priority provider transport hardening with limited blast radius and no evidence of an active widespread outage.
  • merge-risk: 🚨 compatibility: Merging changes existing Anthropic-compatible success streams over 16 MiB from continuing to throwing and cancelling.
  • 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 copied live output from a real local node:http server showing after-fix oversized-stream cancellation near the cap plus negative-control and happy-path cases.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes copied live output from a real local node:http server showing after-fix oversized-stream cancellation near the cap plus negative-control and happy-path cases.
Evidence reviewed

PR surface:

Source +13, Tests +43. Total +56 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 15 2 +13
Tests 1 43 0 +43
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 58 2 +56

What I checked:

Likely related people:

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.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 25, 2026
@wangmiao0668000666
wangmiao0668000666 force-pushed the fix/anthropic-transport-stream-bound branch from 76706ef to fc43cb4 Compare June 25, 2026 08:30
@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Fixed both blockers from the r1 review.

1. Real behavior proof added. Local-only _proof_anthropic_stream.mts (not committed, matching Alix-007 #96607 pattern) drives the production createSseByteGuard against a real node:http server on 127.0.0.1 streaming a 64 MiB Content-Length-less body. 4/4 PASS:

=== Anthropic streaming success-body bounded read (cap=16777216, would-stream≈67108864) ===

PASS  Anthropic streaming success body bounded: rejected with "Anthropic Messages success body exceeded 16777216 bytes (received 16836692)..."; bytesSent=20971520 (< 67108864); server.aborted=true; overflowed=true; cancelled=true
PASS  cap-trace: bounded reader cancelled at 16756082 bytes (full body = 67108864); server.bytesSent=19922944; server.aborted=true
PASS  negative control: small body fully drained (8388608 bytes >= 8388608); cap did not trigger; overflowed=false; cancelled=false; aborted=false
PASS  happy path: small valid Anthropic SSE response drained end-to-end (88 bytes <= 16777216)

=== All Anthropic streaming success-body bounded-read assertions passed ===

2. [P3] overflowed() / cancelled() separated. The P3 finding flagged that overflowed() returned the same cancelled flag set by explicit cancel(). The helper now tracks two separate flags:

  • overflowed() — true ONLY when the byte cap is exceeded
  • cancelled() — true on either overflow or explicit cancel

The proof script asserts both behaviors: after overflow overflowed=true; cancelled=true, and the small-body control has overflowed=false; cancelled=false. PR body "Risk" section documents this fix.

3. [P1] 16 MiB cap compatibility — unchanged, still requires maintainer acceptance. The cap matches the established readProviderJsonResponse 16 MiB default for non-streaming Anthropic success bodies. The ClawSweeper r1 finding flagged this as a "compatibility boundary maintainers should notice"; the PR body and merge-risk: 🚨 compatibility label both surface this for the maintainer pass.

Force-pushed as fc43cb4bc4. Final size: 3 files, 145 LoC, size: S.

@clawsweeper

clawsweeper Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 25, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 25, 2026
wangmiao0668000666 added a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 25, 2026
…(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.
vincentkoc pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 27, 2026
… 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).
vincentkoc pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 27, 2026
… 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).
vincentkoc added a commit that referenced this pull request Jun 27, 2026
… 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]>
wangmiao0668000666 added a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 27, 2026
… 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]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 27, 2026
… 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]>
@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

🦞 upgrades applied (PR body only):

  1. 🦞fix: send plain string message in tau-rpc instead of structured object #25: Design Rationale — 4 WHYs (separate helper, 16 MiB cap rationale, overflowed/cancelled separation, core-internal vs SDK)
  2. Telegram integration #16: Risk checklist format (4-question checklist)
  3. twilio: rewrite typing indicator with direct API calls and robust retry #19: Sibling PR refs — fix(agents): bound Anthropic error streams #95108 error-body cap, fix(anthropic): bound streaming 200 success-body SSE reads at 16 MiB (provider path) #96723/fix(runtime/proxy): bound streaming success-body SSE reads at 16 MiB #96768 sequenced surfaces
  4. Heartbeat skipped when last inbound message is from a group chat #20: New helper createSseByteGuard declared (87 LoC)

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.
@wangmiao0668000666
wangmiao0668000666 force-pushed the fix/anthropic-transport-stream-bound branch from fc43cb4 to 7a45023 Compare June 27, 2026 16:06
wangmiao0668000666 added a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 27, 2026
…(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.
wangmiao0668000666 added a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 27, 2026
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).
@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Rebased onto current main. The createSseByteGuard helper (originally 87 LoC in this PR) is now in main (via #96762 merge), so the diff no longer includes the helper creation — only the application. Body updated to "Reuses the existing helper from main".

Diff: 2 files, +58/-2 (no more streaming-byte-guard.ts creation). Test count: 78/78.

@clawsweeper

clawsweeper Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

xydigit-zt pushed a commit to xydigit-zt/xydigit-zt-openclaw that referenced this pull request Jun 28, 2026
… 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]>
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
… 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]>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
… 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]>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
… 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]>
@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

Closing as duplicate of #96723 / superseded by #96762.

Decision: This PR and #96723 both bound Anthropic streaming SSE at 16 MiB via createSseByteGuard. The shared helper landed via #96762 (openai-chatgpt-responses) on 2026-06-27, so the helper-creation diff in this PR is no longer needed. The remaining per-provider call-site addition is now a one-line wrap.

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 createSseByteGuard from the now-merged shared helper and wraps the Anthropic 200 success-body stream — small enough that a fresh reviewer can land it cleanly. Happy to draft this if you want a focused PR.

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

wheakerd pushed a commit to wheakerd/clawdbot that referenced this pull request Jul 15, 2026
… 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. 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.

1 participant