Agent silently fails on inbound images: replayed image attachments sent to Anthropic with non-base64 source.base64
Version: 2026.5.24-beta.2 (abb43c9) (Homebrew install, macOS)
Channels seen: Discord (also reproduces on the OpenAI/Responses failover path)
Symptom
When a user sends an image in a channel, the agent often produces no reply at all. The gateway run ends with isError=true and the Anthropic API rejects the entire turn:
embedded run agent end: isError=true model=claude-sonnet-4-6 provider=anthropic
error=LLM request rejected:
messages.337.content.1.image.source.base64: string argument should contain only ASCII characters
On the OpenAI/Responses failover the same underlying data triggers:
Invalid 'input[0].output[1].image_url'. Expected a base64-encoded data URL ...
but got an invalid base64-encoded value.
Root cause
The inbound image file on disk is valid and base64-encodes to pure ASCII. The corruption happens during per-turn message assembly: a replayed/inline image attachment reaches the provider mapper with its data field holding a raw (latin1/binary) byte string instead of base64.
- The current-turn path is correct —
resolveAgentTurnAttachments in agent-turn-attachments reads the buffer and does data: buffer.toString("base64").
- The inline/replayed-history path forwards
data untouched:
resolveInlineAgentImageAttachments → { mediaType: image.mimeType, data: image.data }
- The Anthropic provider mapper (
provider-stream) then builds, for every block:
source: { type: "base64", media_type: block.mimeType, data: block.data }
If data is unencoded bytes, source.base64 contains non-ASCII and the API rejects the whole request — so a single bad historical image block poisons every subsequent turn in a long-lived session (images are stored as path-refs and re-hydrated each turn).
This looks like the counterpart to #83466 ("hydrate current inbound image attachments"), which fixed the current attachment path but not the replayed/inline one.
Reproduction
- Send an image to the agent in a Discord channel.
- Continue the conversation so the image becomes a history entry in a long-lived session.
- Send any follow-up message — the turn is rejected with the
image.source.base64 ... only ASCII characters error and the agent goes silent.
Suggested fix
Guarantee data is ASCII base64 at the point it becomes source.base64. A safe, idempotent guard (valid base64 is always ASCII, so correct payloads pass through untouched):
const toAsciiBase64 = (d) =>
(typeof d === "string" && !/^[A-Za-z0-9+/=\s]*$/.test(d))
? Buffer.from(d, "latin1").toString("base64")
: d;
Better still, fix the source so the inline/replayed-history hydration base64-encodes like the current-turn path does. The same raw-data passthrough also exists on the OpenAI/Responses mappers and should get the same treatment.
Agent silently fails on inbound images: replayed image attachments sent to Anthropic with non-base64
source.base64Version:
2026.5.24-beta.2 (abb43c9)(Homebrew install, macOS)Channels seen: Discord (also reproduces on the OpenAI/Responses failover path)
Symptom
When a user sends an image in a channel, the agent often produces no reply at all. The gateway run ends with
isError=trueand the Anthropic API rejects the entire turn:On the OpenAI/Responses failover the same underlying data triggers:
Root cause
The inbound image file on disk is valid and base64-encodes to pure ASCII. The corruption happens during per-turn message assembly: a replayed/inline image attachment reaches the provider mapper with its
datafield holding a raw (latin1/binary) byte string instead of base64.resolveAgentTurnAttachmentsinagent-turn-attachmentsreads the buffer and doesdata: buffer.toString("base64").datauntouched:resolveInlineAgentImageAttachments→{ mediaType: image.mimeType, data: image.data }provider-stream) then builds, for every block:source: { type: "base64", media_type: block.mimeType, data: block.data }If
datais unencoded bytes,source.base64contains non-ASCII and the API rejects the whole request — so a single bad historical image block poisons every subsequent turn in a long-lived session (images are stored as path-refs and re-hydrated each turn).This looks like the counterpart to #83466 ("hydrate current inbound image attachments"), which fixed the current attachment path but not the replayed/inline one.
Reproduction
image.source.base64 ... only ASCII characterserror and the agent goes silent.Suggested fix
Guarantee
datais ASCII base64 at the point it becomessource.base64. A safe, idempotent guard (valid base64 is always ASCII, so correct payloads pass through untouched):Better still, fix the source so the inline/replayed-history hydration base64-encodes like the current-turn path does. The same raw-
datapassthrough also exists on the OpenAI/Responses mappers and should get the same treatment.