Skip to content

fix(provider-usage): bound usage JSON response reads to prevent OOM#97610

Closed
cxbAsDev wants to merge 1 commit into
openclaw:mainfrom
cxbAsDev:fix/bound-usage-json-response
Closed

fix(provider-usage): bound usage JSON response reads to prevent OOM#97610
cxbAsDev wants to merge 1 commit into
openclaw:mainfrom
cxbAsDev:fix/bound-usage-json-response

Conversation

@cxbAsDev

Copy link
Copy Markdown
Contributor

Summary

Replace unbounded response.json() with readProviderJsonResponse in the readUsageJson shared helper to cap provider usage API response bodies at 16 MiB. This prevents unbounded buffering of responses from all seven providers that call this helper: Claude, Gemini, Codex, Z.AI, DeepSeek, MiniMax, and OpenAI-compatible embeddings.

Real behavior proof (required for external PRs)

Behavior addressed: Unbounded response.json() replaced with readProviderJsonResponse (16 MiB cap).

Real environment tested: Linux, Node 22, OpenClaw main @ 87db23e

Exact steps or command run after fix:

node --import tsx proof-readUsageJson.mts

Proof harness:

// Creates a streaming 64 MiB body with 1 MiB chunks.
// The bounded reader must stop at the 16 MiB cap, cancel
// the stream, and return { ok: false, snapshot }.
const { response, state } = makeOversizedStream();
const result = await readUsageJson("anthropic", response);

Evidence after fix:

cap: 16777216 bytes (16 MiB)
chunk: 1 MiB, total stream: 64 MiB
chunks enqueued: 17
stream cancelled: true
result.ok: false
snapshot.error: Malformed usage response
bytes buffered: 17825792 (17.0 MiB)
PASS: reader stopped at 17.0 MiB, stream cancelled before full 64 MiB buffering

The harness creates a streaming 64 MiB body with 1 MiB chunks. The bounded reader pulls only 17 chunks (stopping at the 16 MiB cap), cancels the stream, and returns the error snapshot. Before the fix, response.json() would have buffered the entire 64 MiB before parsing.

Observed result after fix: readProviderJsonResponse caps provider usage API responses at 16 MiB. Streaming bodies are cancelled at the cap instead of being fully buffered. The existing { ok: false, snapshot } error contract is preserved for both overflow and malformed-JSON errors.

All existing tests pass: 51 tests across 5 caller test files (claude, codex, deepseek, gemini, zai) plus the shared helpers test file.

What was not tested: Live provider usage API calls against Anthropic, Gemini, Codex, Z.AI, DeepSeek, or MiniMax usage endpoints were not tested.

Risk

Low. The 16 MiB cap matches the convention in provider-http-errors.ts. The existing try-catch in readUsageJson preserves the { ok: false, snapshot } contract — overflow and JSON parse errors both return "Malformed usage response", matching the existing behavior for malformed-JSON responses.

Replace unbounded response.json() with readProviderJsonResponse in
readUsageJson to cap provider usage API response bodies at 16 MiB,
preventing unbounded buffering from all seven providers (claude,
gemini, codex, z.ai, deepseek, minimax, and openai-compatible).

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

clawsweeper Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Close as duplicate/superseded because #96360 already owns the same shared provider-usage JSON OOM fix with focused tests, sufficient proof, and a ready-for-maintainer-review ClawSweeper verdict; this branch's useful helper-reuse idea can be folded there instead of keeping two landing candidates open.

Root-cause cluster
Relationship: duplicate
Canonical: #96360
Summary: The current PR and #96360 both address the same unbounded shared provider-usage JSON helper; the older PR is the canonical open landing path because it has focused tests, sufficient proof, and a ready-for-maintainer-review status.

Members:

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

Canonical path: Close this duplicate and concentrate review on #96360, optionally adopting this branch's readProviderJsonResponse call there if maintainers prefer that helper shape.

So I’m closing this here and keeping the remaining discussion on #96360.

Review details

Best possible solution:

Close this duplicate and concentrate review on #96360, optionally adopting this branch's readProviderJsonResponse call there if maintainers prefer that helper shape.

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

Yes. Source inspection shows current main and v2026.6.10 still use direct response.json() in readUsageJson, and the PR body supplies terminal output from a 64 MiB streaming harness showing bounded cancellation after the change.

Is this the best way to solve the issue?

No. The helper reuse is a reasonable implementation detail, but the best solution is to use the canonical PR with focused tests and proof, folding this helper call into that branch only if maintainers prefer it.

Security review:

Security review cleared: The diff reuses an existing internal bounded JSON reader and does not change secrets, auth, dependencies, workflows, package resolution, or code execution surfaces.

AGENTS.md: found and applied where relevant.

What I checked:

Likely related people:

  • vincentkoc: Live commit metadata shows vincentkoc added the readUsageJson malformed-JSON wrapper and recently touched the shared provider-usage helper and MiniMax usage path for response-body cancellation. (role: recent area contributor; confidence: high; commits: a118e114fe5f, 0c565f3b0ef9; files: src/infra/provider-usage.fetch.shared.ts, src/infra/provider-usage.fetch.minimax.ts, src/infra/provider-usage.fetch.codex.ts)
  • steipete: Commit metadata shows steipete deduplicated provider usage fetch logic into the shared helper structure and originally added the MiniMax usage snapshot path. (role: feature-history owner; confidence: high; commits: badafdc7b3e4, 18b4575e4dcd; files: src/infra/provider-usage.fetch.shared.ts, src/infra/provider-usage.fetch.minimax.ts, src/infra/provider-usage.fetch.codex.ts)
  • Alix-007: Commit metadata shows Alix-007 introduced the adjacent readProviderJsonResponse bounded JSON helper used by this PR. (role: adjacent hardening contributor; confidence: medium; commits: 2592f8a51a4e; files: src/agents/provider-http-errors.ts, src/agents/provider-http-errors.test.ts)
  • IVY-AI-gif: Merged PR metadata shows IVY-AI-gif recently improved MiniMax usage parsing and tests in the sibling path that remains separate from the shared helper. (role: recent MiniMax usage contributor; confidence: medium; commits: 9eb8184f368a; files: src/infra/provider-usage.fetch.minimax.ts, src/infra/provider-usage.fetch.minimax.test.ts)

Codex review notes: model internal, reasoning high; reviewed against 561c713bb1f3.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. 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. P2 Normal backlog priority with limited blast radius. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. 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 Jun 29, 2026
@clawsweeper

clawsweeper Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

@vincentkoc

Copy link
Copy Markdown
Member

Follow-up closeout: the landed replacement is #97659, merged as db2786b.

Reason for replacement instead of editing this branch: maintainer push was rejected on the fork, and the original diff only covered the shared helper path; the landed replacement also bounds the MiniMax success path and Claude OAuth error-body parsing, with focused tests for both. Credit preserved from this PR in the replacement PR body.

@clawsweeper

clawsweeper Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper could not autoclose this item.

Reason: structured ClawSweeper close marker: close-required (sha=1f1c87e6fcbd4b59ff75176db959bd291da17e75)

Usage: /autoclose <maintainer close reason>. I will close this item and any open same-repo items explicitly referenced in the command text.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: XS 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