Repository navigation
Description
Summary
When the read tool opens a PDF, OpenCode attaches the PDF as a file part to the conversation history. On the opencode provider's GLM models (e.g. glm-5.3-flash), the upstream gateway rejects the next request with HTTP 400 json_parse_error. Because the unsupported part stays in history, every subsequent request fails — even plain "continue" prompts — permanently bricking the session. /compact, which drops the attachment from context, recovers it.
Environment
- opencode version: v2.0.24 (latest)
- OS: Arch Linux, kernel 7.2.9-arch1-1 (x86_64)
- Terminal: kitty (TERM=xterm-256color, COLORTERM=truecolor)
- Shell: /usr/bin/bash
- Install/channel: latest
- Active plugins: none found in config
- Provider / model:
opencode/glm-5.3-flash
Reproduction
-
Start a new session with provider
opencode, modelglm-5.3-flash. -
Ask the agent to inspect any PDF file with the
readtool (e.g. a generated report). -
The tool call completes and the PDF is attached to the tool result.
-
The next model request fails with HTTP 400:
Upstream request failed: [json_parse_error] Invalid JSON data: Failed to deserialize the JSON body into the target type: messages[N]: data did not match any variant of untagged enum MessageContent at line 1 column <offset>Nis the index of the message carrying the PDF part. -
Send anything afterwards — even the literal prompt
continue— and the identical error repeats on every request. The session is unusable from that point on. -
Run
/compact(or start a new session). Once the PDF part is out of context, the session works again.
Reproducibility: deterministic. Hit twice today in two independent sessions, 100% failure rate after the PDF part enters history; 6 failed requests total.
Server log evidence (abridged, session IDs shortened):
- Session A:
readon a ~39 KB invoice PDF at 22:10:39 local, then 4×
level=ERROR "Failed to drain Session" cause="AI.Error: Upstream request failed: [json_parse_error] ... messages[136]: ... at line 1 column 349405"(22:10:45–22:12:13), including for the promptcontinue. - Session B: same PDF read, 2×
... messages[51]: ... at line 1 column 163865(22:17:02, 22:17:33); a compaction entry at 22:18:27, session functional afterwards.
Expected Behavior
- OpenCode should normalize PDF tool results into a content type the target provider actually supports (e.g. page images or extracted text), or omit them for providers/upstreams that don't accept file parts.
- Independently, a single unsupported part should not permanently brick a session: on a 400 of this shape, OpenCode could sanitize/drop the offending part and retry.
Actual Behavior
Upstream rejects every request with json_parse_error once a PDF part is in the history; the session cannot be continued at all until compaction removes the part.
Additional Context
- The error text comes from the upstream gateway (z.ai/GLM, Rust/serde-style message), not from OpenCode's own JSON handling — OpenCode is sending a content shape the upstream can't deserialize.
- The byte offsets in the two variants (~164 KB and ~350 KB) are consistent with the position of the PDF-bearing message inside a long request body.
- Workaround:
/compactor a new session; avoidreadon PDFs with this provider/model. - Possibly related to how file parts are mapped for models without native file-part support.
Activity
opencode-agent commentedon Oct 7, 2026
Thanks for the detailed report. I reproduced the OpenCode side of this on v2.0.24 and on the latest
v2branch (8587f817), using a simulated model. Afterreadopens a PDF, the tool result is justPDF read successfully. The PDF goes in a separate user message as an OpenAI Chatfilepart:{"role":"user","content":[{"type":"file","file":{"filename":"report.pdf","file_data":"data:application/pdf;base64,..."}}]}That message is sent again on every later request, including a plain
continue. This matches themessages[N]index in your error and why only/compactrecovers the session.I couldn't confirm the
json_parse_erroritself, because that comes from the OpenCode Zen gateway and I don't have provider access here. A maintainer with Zen access will need to check whether the gateway accepts thisfilepart forglm-5.3-flash.Related issues (closest first):
glm-5.3-flash) breaking on a PDF from areadresult, on v1, with a 422file_data, Z.AI accepts PDF only viafile_url/file_id#49237 / pdf not readable by Z.AI/GLM 5.3 Flash through OpenCode #49028: Z.AI rejects PDFs sent asfile_dataFor now,
/compactor a new session works around it, as you found.pipaacebedo commentedon Oct 11, 2026
Here is a small, non-destructive CLI tool that repairs opencode sessions that get stuck after an agent reads a PDF file while using strict OpenAI-compatible providers (e.g. the
opencode-go/ zen gateway). Written in pure Python (3.8+, stdlib only).Related upstream issue: anomalyco/opencode#48868
When the
readtool runs on a PDF, opencode stores the result in its database with a base64 attachment:{ "type": "tool", "tool": "read", "state": { "output": "PDF read successfully", "attachments": [{ "type": "file", "mime": "application/pdf", "url": "data:application/pdf;base64,..." }] } }On every subsequent turn, the session provider-isolation layer re-injects that media as a synthetic user message whose
contentis an array of blocks (message-v2.ts:380-399). Strict OpenAI-compatible gateways only acceptcontent: stringfor user messages, so every request fails with:The session is permanently bricked: every retry replays the same broken history.
For the affected session (or a single part), it:
fix-backups/part_<id>.json(the full JSON of the part, a few hundred KB — the 6+ GB database file is never copied).state.attachmentsbecomes[]inside a single atomic transaction. The text output ("PDF read successfully"), metadata and tool input are kept untouched. The model already ingested the PDF text while working; only the base64 binary that breaks history replay is discarded.opencodedoes not need to be closed: SQLite WAL mode safely supports the concurrent writer. The-wal/-shmfiles require no special handling.Requirements
Usage
Run the interactive wizard (recommended):
s.Non-interactive commands (for scripting / automation):
python fix-opencode-db.py list [text][PDF xN]python fix-opencode-db.py fix <ses_... | prt_...>python fix-opencode-db.py verify <ses_... | prt_...>python fix-opencode-db.py restore <ses_... | prt_...>--db <path>Backups live in
fix-backups/next to the script, with amanifest.jsonthat maps each part id to its session for session-level restores.Safety notes
writing, abort on any mismatch.
restoreputs everything back byte-for-byte.fixon a session with no PDF attachments is a safe no-op.Troubleshooting
Could not find opencode.db--db "C:\path\to\opencode.db"verify; if it prints OK, the failure is likely a different issue — report it upstreampython fix-opencode-db.py restore ses_...(typeRESTOREto confirm)Root cause (for the curious)
packages/opencode/src/tool/read.ts:306-324—readreturns PDFs as base64 attachments.packages/opencode/src/session/message-v2.ts:147-159—supportsMediaInToolResult()returnsfalsefor@ai-sdk/openai-compatible, so media is extracted into a synthetic user message (lines 380-399) with array content.packages/opencode/src/provider/transform.ts:409-445—unsupportedParts()passes thefileblock through when the model declares PDF support, and never collapses all-text content arrays back to strings, which the OpenAI-compatible SDK requires.Fixes to upstream are discussed in the linked issue.
Disclaimer
This tool edits opencode's local SQLite database directly. Use at your own risk.
It performs strict validation and per-row backups before any change, and no database files are deleted at any point.
Script
fix-opencode-db.py
amitnarw commentedon Oct 14, 2026
Hit this too, on v2.0.28 (latest). Windows 11, ollama-cloud / glm-5.3-flash.
Read a ~400KB PDF with the read tool, worked fine, model even answered questions about it. From the next message onwards every single request failed with invalid message format (ref: ...) = a different ref each time, so at first I thought it was random. Restart didn't help, session was dead. Even "say hi" wouldn't go through.
Dug into the session db and found the read tool had stuffed the entire PDF as base64 into the history. Every new message resends that history, GLM chokes on it, session never recovers.
Fixed it by hand by editing the stored message and swapped the PDF blob with a short text note pointing to the file on disk. Worked again right away, lost nothing except the blob. Same history reads fine on a non-GLM model btw, so it's specifically GLM failing on the PDF part.
Also: the error text here is different from the json_parse_error in the main report (I'm on ollama-cloud, not the opencode provider), so anyone googling "invalid message format" should find this thread.
cep207 commentedon Oct 22, 2026
Same failure mode in OpenCode Desktop, with a plain .txt attachment
(not a PDF, and not from the
readtool) — so this is broader than theread-tool/PDF path.
GLM-5.3-Flashthrough the OpenCode gatewayUpstream request failed: [400] Failed to deserialize the JSON body into the target type: data did not match any variant of untagged enum ChatCompletionRequestUserMessageContent at line 1 column 1357103Upstream request failed: [unsupported_content_type] Messages may contain text, images and video; this model does not read audio or files.Expected: for models without
fileinput support (models.dev), OpenCodeshould either serialize the attachment as text or reject the attachment at
compose time — not brick the whole session.