Skip to content

Images read via the read tool bypass the model capabilities gate — persistent 400 errors and unusable sessions (also triggered by switching models) #51873

Description

@choiinho13-lgtm

Summary

In OpenCode v2, the capability gate that strips unsupported media from user attachments does not apply to images returned by the read tool. The image is stored as a base64 data URI inside the session history (tool part state.content) and is sent to models whose input capability is text-only (e.g. glm-5.3 with input: ["text"]).

The provider then rejects every request:

Error: messages.content.type is invalid, allowed values: ['text']

The failed assistant message persists in history, so all subsequent requests in that session fail with the same 400 — the session becomes permanently unusable. There is no in-app way to remove the media from history.

There is also a second path that involves no tool call at all: a session where a vision-capable model previously read images gets killed by simply switching the session's model to a text-only one, because the stored images are now sent to a model that cannot accept them.

Reproduction — Path A (read tool)

  1. Use a text-only model (e.g. glm-5.3, capabilities input: ["text"])
  2. Ask the agent to read a local image file (the read tool returns it as a base64 data URI)
  3. The image bypasses the capabilities gate and is included in the LLM request
  4. GLM API returns 400; the failed message stays in history; every follow-up turn fails with the same 400 (session dead)

Observed live (2026-09-28): a session accumulated 4 image file parts (~8.5 MB base64) in history; every subsequent request failed with 400 in ~300 ms until the history was manually repaired.

Reproduction — Path B (model switch)

  1. With a vision-capable model (e.g. glm-5.3-flash, input: ["text","image",...]), read an image — this passes, as it should
  2. Switch the session's model to a text-only one (e.g. glm-5.3)
  3. The next request includes the stored image → 400 → session dead

Expected

The request composer should apply the same capability gate to image outputs of the read tool (file parts in tool state.content) as it does to user attachments — replace with a short text notice (or exclude) when the model's input capabilities do not include "image". Because filtering happens at request-composition time, this would also fix Path B (model switching).

Actual

400 messages.content.type is invalid, allowed values: ['text'] on every request; session marked failed. Recovery required directly editing the session DB (replacing base64 blobs in session_message.data with a text notice) — no in-app recovery path exists.

Notes

  • Videos appear to take a different path (excluded from the LLM message), so images and videos are treated asymmetrically.
  • Local workaround in use: a plugin hooking execute.before on the read tool that blocks image reads for text-only models. Effective for Path A; cannot prevent Path B since model switching is not a tool call.
  • Three live occurrences in a single day of normal use, all on GLM.

Environment

  • opencode: v2.0.18 (Windows standalone binary)
  • OS: Windows 11 Pro (10.0.26200), x64
  • Terminal: Orca embedded terminal
  • Shell: cmd.exe
  • Install: release channel
  • Plugins: 15 local files auto-scanned from ~/.config/opencode/plugins (includes the custom read-blocking gate described above)

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions