openclaw - ✅(Solved) Fix OpenAI compat gateway images bypass sanitization/resize, hit Anthropic 5MB limit [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#59913Fetched 2026-04-08 02:38:53
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1referenced ×1

Images sent via the OpenAI-compatible /v1/chat/completions endpoint (as image_url content parts) bypass the image sanitization/resize pipeline. Large images (e.g., Mac Retina screenshots) are forwarded to Anthropic at full resolution and size, hitting Anthropic's 5MB per-image limit and returning Could not process image or image exceeds 5 MB maximum.

A secondary issue: in streaming mode (stream: true), the Anthropic API error is completely swallowed — the client receives only data: [DONE] with no error content.

Error Message

// Event listener (lines 573-581) if (evt.stream === "lifecycle") { const phase = evt.data?.phase; if (phase === "end" || phase === "error") { closed = true; // ← sets closed before catch runs unsubscribe(); writeDone(res); res.end(); } }

// Async catch block (lines 613-628) } catch (err) { if (closed) return; // ← sees closed=true, skips error content writeAssistantContentChunk(res, { content: "Error: internal error", // ← never reached ... }); }

Root Cause

Root Cause

PR fix notes

PR #62070: fix: sanitize OpenAI compat gateway images and surface streaming errors

Description (problem / solution / changelog)

Summary

Fixes #59913

  • Bug 1: Gateway-provided images (via OpenAI compat /v1/chat/completions image_url parts) bypassed the sanitization/resize pipeline. detectAndLoadPromptImages() early-returned existingImages unsanitized when no image refs were found in the prompt text. Large images (e.g. Mac Retina screenshots >5MB) were forwarded to Anthropic at full resolution, hitting the 5MB per-image limit.
  • Bug 2: In streaming mode (stream: true), when a lifecycle "error" event fired before the async catch block could run, the stream was closed with only data: [DONE] — the error was completely swallowed and the client received no error content.

Changes

  • src/agents/pi-embedded-runner/run/images.ts: Apply sanitizeImagesWithLog() to existingImages on the early-return path (no prompt image refs detected)
  • src/gateway/openai-http.ts: Write error content chunk in the lifecycle "error" handler before closing the stream, when no assistant content was already sent

Test plan

  • New test: existingImages are sanitized even when prompt has no image references (images.test.ts)
  • New test: error content appears in SSE stream when lifecycle error fires without prior assistant deltas (openai-http.test.ts)
  • All 32 existing image tests pass
  • All 6 existing gateway openai-http tests pass

AI-assisted: Built with Claude Code. Fully tested locally. I understand what the code does.

🤖 Generated with Claude Code

Changed files

  • src/agents/pi-embedded-runner/run/images.test.ts (modified, +17/-0)
  • src/agents/pi-embedded-runner/run/images.ts (modified, +4/-1)
  • src/gateway/openai-http.test.ts (modified, +34/-0)
  • src/gateway/openai-http.ts (modified, +13/-0)

Code Example

curl -X POST https://AGENT_HOST/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "model": "openclaw",
    "messages": [{
      "role": "user",
      "content": [
        {"type": "text", "text": "Describe this image"},
        {"type": "image_url", "image_url": {"url": "data:image/png;base64,<large-base64-png>"}}
      ]
    }],
    "stream": false
  }'

---

// images.ts lines 500-507
const allRefs = detectImageReferences(params.prompt);

if (allRefs.length === 0) {
    return {
      images: params.existingImages ?? [],  // ← returned WITHOUT sanitization
      detectedRefs: [],
      loadedCount: 0,
      skippedCount: 0,
    };
}

---

// Event listener (lines 573-581)
if (evt.stream === "lifecycle") {
    const phase = evt.data?.phase;
    if (phase === "end" || phase === "error") {
        closed = true;       // ← sets closed before catch runs
        unsubscribe();
        writeDone(res);
        res.end();
    }
}

// Async catch block (lines 613-628)
} catch (err) {
    if (closed) return;      // ← sees closed=true, skips error content
    writeAssistantContentChunk(res, {
        content: "Error: internal error",  // ← never reached
        ...
    });
}

---

if (allRefs.length === 0) {
    const sanitized = await sanitizeImagesWithLog(
      params.existingImages ?? [],
      "prompt:images",
      { maxDimensionPx: params.maxDimensionPx },
    );
    return {
      images: sanitized,
      detectedRefs: [],
      loadedCount: 0,
      skippedCount: 0,
    };
}
RAW_BUFFERClick to expand / collapse

Summary

Images sent via the OpenAI-compatible /v1/chat/completions endpoint (as image_url content parts) bypass the image sanitization/resize pipeline. Large images (e.g., Mac Retina screenshots) are forwarded to Anthropic at full resolution and size, hitting Anthropic's 5MB per-image limit and returning Could not process image or image exceeds 5 MB maximum.

A secondary issue: in streaming mode (stream: true), the Anthropic API error is completely swallowed — the client receives only data: [DONE] with no error content.

Steps to Reproduce

  1. Run an OpenClaw gateway with an Anthropic provider
  2. Send a large image (>5MB decoded) via the OpenAI compat endpoint:
curl -X POST https://AGENT_HOST/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "model": "openclaw",
    "messages": [{
      "role": "user",
      "content": [
        {"type": "text", "text": "Describe this image"},
        {"type": "image_url", "image_url": {"url": "data:image/png;base64,<large-base64-png>"}}
      ]
    }],
    "stream": false
  }'
  1. Non-streaming: Response is 200 OK with error text in content: LLM request rejected: messages.0.content.1.image.source.base64: image exceeds 5 MB maximum
  2. Streaming: Response is 200 OK with only data: [DONE] — error is completely lost

Root Cause

Bug 1: Gateway images skip sanitization

In src/agents/pi-embedded-runner/run/images.ts, detectAndLoadPromptImages() has an early return when no image references are found in the prompt text:

// images.ts lines 500-507
const allRefs = detectImageReferences(params.prompt);

if (allRefs.length === 0) {
    return {
      images: params.existingImages ?? [],  // ← returned WITHOUT sanitization
      detectedRefs: [],
      loadedCount: 0,
      skippedCount: 0,
    };
}

When images arrive via the OpenAI compat gateway (openai-http.ts), they are extracted from image_url parts and passed as existingImages. The prompt text contains only the user's text (no file path references), so allRefs.length === 0 and the images skip sanitizeImagesWithLog() entirely.

The sanitization pipeline (via sanitizeContentBlocksImagesresizeImageBase64IfNeeded) would normally resize images exceeding 1200px to JPEG and enforce size limits. Without it, large PNGs pass through to Anthropic at full resolution.

Affected path:

  1. Gateway: openai-http.tsparseImageUrlToSource()extractImageContentFromSource()ImageContent
  2. Agent command: attempt-execution.ts:458 passes images to runEmbeddedPiAgent
  3. Embedded runner: attempt.ts:1477 calls detectAndLoadPromptImages({ existingImages: params.images })
  4. Early return at line 502 → images go directly to activeSession.prompt(text, { images }) unsanitized
  5. pi-ai convertMessages() converts to Anthropic format → 400 error from API

Working path (for comparison): Images detected from prompt text (file paths, media URIs) go through the full pipeline: loadImageFromRefmergePromptAttachmentImagessanitizeImagesWithLog → resize to 1200px JPEG.

Bug 2: Streaming error swallowed

In src/gateway/openai-http.ts, there's a race between the lifecycle event listener and the async catch block:

// Event listener (lines 573-581)
if (evt.stream === "lifecycle") {
    const phase = evt.data?.phase;
    if (phase === "end" || phase === "error") {
        closed = true;       // ← sets closed before catch runs
        unsubscribe();
        writeDone(res);
        res.end();
    }
}

// Async catch block (lines 613-628)
} catch (err) {
    if (closed) return;      // ← sees closed=true, skips error content
    writeAssistantContentChunk(res, {
        content: "Error: internal error",  // ← never reached
        ...
    });
}

When the lifecycle "error" event fires first, it closes the stream. The catch block then sees closed = true and returns without writing any error content.

Expected Behavior

  1. Gateway-provided images should go through the same sanitizeImagesWithLog pipeline as prompt-detected images (resize to max 1200px, JPEG conversion, size enforcement)
  2. Streaming mode should surface errors to the client (either as an SSE error event or as content before [DONE])

Suggested Fix

For Bug 1

In detectAndLoadPromptImages(), apply sanitization to existingImages even on the early-return path:

if (allRefs.length === 0) {
    const sanitized = await sanitizeImagesWithLog(
      params.existingImages ?? [],
      "prompt:images",
      { maxDimensionPx: params.maxDimensionPx },
    );
    return {
      images: sanitized,
      detectedRefs: [],
      loadedCount: 0,
      skippedCount: 0,
    };
}

For Bug 2

In the streaming handler, ensure the catch block writes the error before the lifecycle listener can close the stream, or write the error in the lifecycle error handler itself.

Environment

  • OpenClaw version: latest (as of 2026-04-02)
  • Provider: Anthropic (claude-sonnet-4-5-20250929)
  • pi-ai: 0.64.0
  • Anthropic SDK: 0.81.0
  • Hosting: GKE Autopilot (gVisor), accessed via OpenAI compat gateway

Impact

Any client sending images >5MB (decoded) via the OpenAI-compatible API will get silent failures in streaming mode. Common scenario: Mac Retina screenshots (5120×2880 PNG, typically 3-10MB) pasted into a chat UI that uses the OpenAI format.

extent analysis

TL;DR

Apply sanitization to images provided through the OpenAI-compatible gateway and modify the streaming handler to surface errors to the client.

Guidance

  1. Sanitize images: In detectAndLoadPromptImages(), apply sanitizeImagesWithLog to existingImages to resize and enforce size limits, ensuring images are processed similarly to those detected from prompt text.
  2. Modify streaming handler: Adjust the streaming handler to write error content before the lifecycle listener closes the stream, or write the error in the lifecycle error handler itself to prevent errors from being swallowed.
  3. Verify image processing: Test the updated detectAndLoadPromptImages function with large images to ensure they are properly resized and do not exceed the 5MB limit.
  4. Test streaming error handling: Verify that errors are correctly surfaced to the client in streaming mode by intentionally triggering an error and checking the response.

Example

// Example of sanitizing existing images in detectAndLoadPromptImages
if (allRefs.length === 0) {
    const sanitized = await sanitizeImagesWithLog(
      params.existingImages ?? [],
      "prompt:images",
      { maxDimensionPx: params.maxDimensionPx },
    );
    return {
      images: sanitized,
      detectedRefs: [],
      loadedCount: 0,
      skippedCount: 0,
    };
}

Notes

  • The provided fix assumes that sanitizeImagesWithLog is the correct function to use for sanitizing images and that it correctly resizes images to the desired maximum dimension.
  • The modification to the streaming handler requires careful consideration to ensure that errors are properly handled and surfaced to the client without introducing additional issues.

Recommendation

Apply the suggested fix to sanitize images and modify the streaming handler to ensure that errors are properly handled and surfaced to the client. This should resolve the issues with large images being sent to Anthropic and errors being swallowed in streaming mode.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix OpenAI compat gateway images bypass sanitization/resize, hit Anthropic 5MB limit [1 pull requests, 1 participants]