Repository navigation
Description
Description
Inline file attachments whose base64 payload is larger than 7,077,879 characters (about 5.3 MB of raw file data) cannot be decoded by the session message API. The request fails with failed to decode session message and HTTP 500, which makes the whole session unopenable even though only one attachment is too large.
The limit lives in the v2 Prompt.Base64 schema, and data is a required field of Prompt.FileAttachment, so there is no supported way to reference a large file instead of inlining it. Sessions that attached a PDF larger than ~5.3 MB therefore cannot be represented at all.
The threshold was measured by bisection against the running server:
base64 data length |
result |
|---|---|
| 7,066,668 | OK |
| 7,077,876 | OK |
| 7,077,880 | failed to decode |
| 7,200,000 | failed to decode |
The limit is per attachment, not per message and not per response:
| attachments | total base64 | response size | result |
|---|---|---|---|
| 1 × 4,000,000 | 5,333,336 | 5.5 MB | OK |
| 2 × 3,000,000 | 8,000,000 | 8.2 MB | OK |
| 2 × 4,000,000 | 10,666,672 | 10.9 MB | OK |
| 1 × 5,400,000 | 7,200,000 | — | failed to decode |
Server log:
level=ERROR message="failed to decode session message" sessionID=ses_… messageID=msg_…
level=INFO message="Sent HTTP response" http.method=GET http.url=/api/session/ses_…/message http.status=500
OpenCode version
2.1.2 (the CLI bundled with the Desktop app, ai.opencode.desktop/cli/2.1.2-x64). The standalone CLI on PATH was 2.0.26. Install/channel: latest.
Operating System
Windows 11 Education, 10.0.26300 (win32 x64)
Terminal
TERM=xterm-256color, no TERM_PROGRAM/COLORTERM (not Windows Terminal). Shell: C:\WINDOWS\system32\cmd.exe.
Plugins
None — ~/.config/opencode/opencode.jsonc contains only $schema.
Steps to reproduce
- Create a user message with an inline attachment of roughly 5.4 MB (e.g. a PDF), so that its base64
datastring is about 7.2 MB long. - Read the session messages:
GET /api/session/{sessionID}/message. - Observe HTTP 500 and the log line above.
Expected behavior
Either of these would be reasonable:
- Inline attachments should be allowed up to a size that covers real-world documents (a multi-hundred-page PDF is common), with the limit documented and enforced at upload time rather than at read time.
- Or large attachments should be stored out-of-band (a blob/file table plus a reference), so that
Prompt.FileAttachmentdoes not need to inline the whole file.
At minimum, the failure should be reported per attachment rather than making the entire session unreadable.
Actual behavior
A single oversized attachment makes GET /api/session/{id}/message return HTTP 500 for the whole session, so the session disappears from the Desktop app with UnknownError: Unexpected server error. Check server logs for details.
Additional context
- This was found while recovering three sessions that a v1→v2 upgrade had left unreadable; see the companion issue about the migration writing stub rows for
opencode-gosessions. - The limit bites exactly on the kind of file a user is likely to attach. In the affected session the three PDFs were 7.3 MB, 7.3 MB and 7.4 MB raw (9.8 MB base64 each), while the attachments small enough to pass totalled 1.3 MB.
Prompt.FileAttachmentrequiresdata,mimeandsource.sourcedoes support{type:"uri", uri}as well as{type:"inline"}, but aurisource still requiresdatato be present, so it cannot be used to avoid the limit.- Local workaround used: drop the oversized attachment from the message. The original bytes remain available in the legacy
parttable ofopencode.db.
Activity
geverl commentedon Oct 22, 2026
Companion issue: #58041 - v1 sessions with the
opencode-goprovider fail to decode after upgrade (HTTP 500).That is the separate defect that made the three sessions unreadable in the first place; this issue covers the attachment size limit that independently prevents large PDFs from being represented at all.
opencode-agent commentedon Oct 22, 2026
Thanks for the detailed report and the bisection tables. I think this is a duplicate of #45558.
The ~7.08M-character threshold isn't an intended size limit.
Prompt.Base64inpackages/schema/src/prompt.tschecks base64 with the regex^(?:[A-Za-z0-9+/]{4})*(?:…)?$. In the Bun runtime that ships with OpenCode, that regex silently returnsfalsefor valid base64 once the string is a few MB long. That's why the limit is per attachment and not per message or response. The same schema runs when a stored message is read back, so one large attachment makes the whole session fail to decode. The limit the code actually sets for an attachment is 20 MB. A fix that replaces the regex is open in #51272. Since your stored data is valid base64, sessions like yours should load again once that fix ships.Possible duplicates and related issues, ordered by likelihood:
Prompt.Base64regex failing on multi-MB attachments (fix PR fix(schema): validate canonical base64 without a backtracking regex #51272)./movesilently does nothing when a large inline-PDF part from v1 fails to decode. Same underlying decode failure.opencode-gosessions.The bot will close this issue as a duplicate in 1 day unless you reply explaining how your problem is different. Your suggestions to enforce limits when a file is uploaded and to report decode failures per attachment are worth raising on #45558 or #51272.
geverl commentedon Oct 22, 2026
Thanks for the diagnosis — that matches what I measured, and I'm happy for this one to be closed as a duplicate of #45558. The Bun regex behaviour explains my results exactly, and I'd had attributed the threshold to an intentional size limit.
Three things worth adding, and one correction.
Precise threshold and controls, usable as a regression test
Bisected against a running 2.1.2 server, one attachment per message, valid base64, everything else in the message valid:
datalengthThe limit is per attachment, not per message and not per response — these all decode fine:
One thing that seems worth flagging: the code intends to allow 20 MB, but the failure actually surfaces at ~5.3 MB of raw file data (7.08M base64 characters), so in practice attachments are rejected roughly 4× earlier than the intended limit suggests. It would be worth making sure the replacement in #51272 is exercised at ≥ 8 MB, and that the 20 MB path is actually reachable rather than failing at some other bound.
A caveat about already-broken data
Because the fix is in the schema, it should make other users' stored sessions load again with no action on their part — which is the good outcome. For my own data it won't be automatic: I had already worked around this locally by dropping the three oversized attachments from the affected user messages, so the fix won't restore them for me. The original bytes are still in the legacy
parttable ofopencode.db, so I can restore them myself if that becomes desirable. If a data migration is planned as part of the fix, it would have been better not to have dropped them — but no action is needed from anyone for my case.Correction: #58041 is not the same bug
Your duplicate list also includes #58041. That's my other report, and I don't think it shares the root cause — it involves no base64 at all:
New session - 2026-10-18T02:09:41.250Zcontains one part in total, no file attachments, and its largest stored blob is 414 bytes. It still returned HTTP 500.messagetable and the v2session_messagetable, all 49 produced by provideropencode-gowere migrated as stubs, and none of the 131 produced by provideropencodewere:The stub contains
"model":{}and no"time", so the migration is losing the model reference and the timestamp — it isn't failing on a long string.reasoningEncryptedContentin those sessions maxes out at 21,906 characters, far below any threshold where this regex misbehaves.I've put the same evidence on #58041. If it turns out the model lookup in the v1→v2 migration does route through the same base64 check, I'd be glad to be wrong — but on the evidence above the two look independent, and #58041 needs a different fix from #51272.
UX points
I raised these two on #58042 before your reply; moving them to #45558 as you suggested:
GET /api/session/{id}/messagewith a 500. The current blast radius is the entire session, which is how three of my sessions disappeared without any in-app indication of what was wrong.opencode-agent commentedon Oct 23, 2026
Closing this as a duplicate of #45558, since there was no reply saying how it's different. The ~5.3 MB per-attachment limit and the HTTP 500 on
GET /api/session/{id}/messageboth come from thePrompt.Base64regex failing on multi-MB base64. #55110 reported the same symptom when a session is loaded and was closed the same way. Closing just keeps the discussion in one place; the bug is still open. The proposed fix, PR #51272, hasn't been merged yet, so please subscribe to #45558 or #51272 to follow it. Since your stored data is valid base64, the affected sessions should load again once a fix like that ships. Your ideas about enforcing limits when a file is uploaded, out-of-band storage (see #46173) and reporting decode failures per attachment are worth raising on #45558 or #51272. If your problem turns out to be different, feel free to reply here.