You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
provider: PDF attachment bricks session on GLM models (upstream json_parse_error at messages[N]) #53809
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.
Start a new session with provider opencode, model glm-5.3-flash.
Ask the agent to inspect any PDF file with the read tool (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>
N is 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: read on 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 prompt continue.
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: /compact or a new session; avoid read on PDFs with this provider/model.
Possibly related to how file parts are mapped for models without native file-part support.
Thanks for the detailed report. I reproduced the OpenCode side of this on v2.0.24 and on the latest v2 branch (8587f817), using a simulated model. After read opens a PDF, the tool result is just PDF read successfully. The PDF goes in a separate user message as an OpenAI Chat file part:
That message is sent again on every later request, including a plain continue. This matches the messages[N] index in your error and why only /compact recovers the session.
I couldn't confirm the json_parse_error itself, 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 this file part for glm-5.3-flash.
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).
On every subsequent turn, the session provider-isolation layer re-injects that media as a synthetic user message whose content is an array of blocks (message-v2.ts:380-399). Strict OpenAI-compatible gateways only accept content: string for user messages, so every request fails with:
422 messages.N.user.content.str: Input should be a valid string
400 Upstream request failed: [400] Request validation failed
The session is permanently bricked: every retry replays the same broken history.
For the affected session (or a single part), it:
Backs up each affected row to fix-backups/part_<id>.json (the full JSON of the part, a few hundred KB — the 6+ GB database file is never copied).
Patches the stored part so state.attachments becomes [] 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.
Verifies the result and prints a summary.
opencode does not need to be closed: SQLite WAL mode safely supports the concurrent writer. The -wal / -shm files require no special handling.
Requirements
Python 3.8+ (standard library only)
Works on Windows, macOS and Linux (arrow-key menu needs an ANSI-capable terminal; Windows Terminal, cmd and most POSIX terminals are fine)
Usage
Run the interactive wizard (recommended):
python fix-opencode-db.py
Type a search text (or press Enter for the 40 most recent sessions).
Pick a session with the arrow keys + Enter.
Review the detected parts (tool, file path, size) and confirm with s.
Backups live in fix-backups/ next to the script, with a manifest.json that maps each part id to its session for session-level restores.
Safety notes
Non-destructive by design: one-row transactions, strict JSON validation before
writing, abort on any mismatch.
Always creates a row backup first; restore puts everything back byte-for-byte.
The database file itself is never copied, moved or rewritten by bulk operations.
Any non-PDF attachments (images) are left untouched.
Running fix on a session with no PDF attachments is a safe no-op.
Troubleshooting
Symptom
Fix
Could not find opencode.db
Pass --db "C:\path\to\opencode.db"
Session still fails after fix
Run verify; if it prints OK, the failure is likely a different issue — report it upstream
Need to undo
python fix-opencode-db.py restore ses_... (type RESTORE to confirm)
Root cause (for the curious)
packages/opencode/src/tool/read.ts:306-324 — read returns PDFs as base64 attachments.
packages/opencode/src/session/message-v2.ts:147-159 — supportsMediaInToolResult() returns false for @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 the file block 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.
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.
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.