Skip to content

Bug: Oversized image in tool result causes infinite error loop #2055

@jccrump

Description

@jccrump

Bug: Oversized image in tool result causes infinite error loop

Summary

When the read tool returns an image that exceeds Anthropic's 5MB base64 limit, the API returns a 400 invalid_request_error. The oversized image stays in message history, so every subsequent API call fails with the same error — creating an infinite loop that locks up the chat.

Error

Error: 400
{"type":"error","error":{"type":"invalid_request_error","message":"messages.31.content.0.tool_result.content.1.image.source.base64: image exceeds 5 MB maximum: 5529508 bytes > 5242880 bytes"},"request_id":"req_011CYwW5ktEeTQ8L5jKqt4G4"}

Root Cause

Three gaps in the image size handling:

1. image-resize.js — Fallback paths return oversized originals

When Photon fails to load (loadPhoton() returns null) or image processing throws in the catch block, resizeImage() returns the original unchanged image — which may be >5MB:

// Line ~33: Photon not available
if (!photon) {
    return { data: img.data, ... wasResized: false }; // ← original, could be >5MB
}

// Line ~152: catch block
catch {
    return { data: img.data, ... wasResized: false }; // ← original, could be >5MB
}

The "last resort" return (line ~141) also doesn't guarantee the result is under the limit.

2. read.js — No post-resize size validation

The read tool calls resizeImage() and blindly sends whatever comes back — no final check that the result is actually under 5MB:

const resized = await resizeImage({ type: "image", data: base64, mimeType });
// No size check here
content = [
    { type: "text", text: textNote },
    { type: "image", data: resized.data, mimeType: resized.mimeType }, // ← could be >5MB
];

3. agent-session.js — No recovery from image-too-large 400 errors

_isRetryableError() only matches 429/500/502/503/504/overloaded/rate-limit errors. The 400 invalid_request_error for oversized images is NOT matched, so:

  • The error gets stored as an assistant message with stopReason: "error"
  • The oversized image tool result stays in message history
  • Every subsequent agent.continue() or new user message sends the same oversized image → same 400 → infinite loop

Proposed Fix

Layer 1: image-resize.js

  • When Photon is unavailable or processing fails AND the original exceeds maxBytes, return null instead of the original
  • Add a comment that callers must handle null return

Layer 2: read.js

  • Handle null return from resizeImage() — return a text-only error explaining the image is too large
  • Add a final safety check: if the resized result is still >5MB, return text error instead

Layer 3: agent-session.js

  • In the agent_end handler, detect image exceeds 5 MB errors before checking retryable errors
  • Strip oversized image blocks from message history (replace with text explanations)
  • Remove the error assistant message so the agent can continue cleanly

Environment

  • pi-coding-agent (npm, macOS ARM64)
  • Anthropic Claude API (5MB per-image base64 limit)
  • Photon loads successfully — issue occurs in edge cases where resize still exceeds limit, or in the fallback/catch paths

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions