Repository navigation
Description
Summary
POST /api/session/{sessionID}/move returns HTTP 200 (CLI exit code 0, empty body) but silently rolls back when the pre-move "drain Session" step throws Session.MessageDecodeError. The session's project_id is left unchanged, so the client believes the move succeeded while nothing was persisted. In my case the decode error is triggered by a V1-era message part that inlines a ~28 MB PDF as a base64 data: URL (~37.6 MB part), which V2's decoder cannot handle.
Two distinct defects are involved:
movereports success on failure — a write endpoint must not return 200/exit-0 when it aborts. It should surface an error.- V1 large inline-PDF file part fails to decode in V2 — a V1→V2 compatibility regression; the migration guide states V1 data should keep working.
Environment
- opencode version: v2.0.27
- OS: Windows 11 Home (zh-CN), 10.0.26200.0, x64
- Terminal: Windows Terminal (
TERM=xterm-256color;TERM_PROGRAMunset) - Shell: Windows PowerShell 5.1 (
ComSpec=C:\WINDOWS\system32\cmd.exe) - Install/channel: release /
latestchannel, npm global install (opencode-ai) - Active plugins: none found in config
Reproduction
Context: recovering sessions orphaned by the known V1→V2 attribution bug (#50551), where non-git-directory sessions keep project_id='global' and never appear in the per-directory picker. POST /api/session/{id}/move is the documented workaround. It worked for 59 of 61 sessions; the 2 that contained a large inline-PDF attachment failed silently.
- In V1 (e.g. 1.14.x), in a non-git directory, create a session and attach a large PDF (~28 MB). V1 stores it as a message
filepart whoseurlis an inlinedata:application/pdf;base64,...string (~37.6 MB for the whole part). - Upgrade to V2 (2.0.27) against the same
opencode.db; let the V1→V2 migration run. - Call the move workaround to re-home the session to its directory's real project:
opencode api post /api/session/<sessionID>/move --data '{"directory":"C:\\Users\\<user>"}'
- Observe exit code 0 and an empty response body (apparent success).
- Query the session again:
opencode api get /api/session/<sessionID>→projectIDis stillglobal; the DB row'sproject_id/pathare unchanged.
Expected Behavior
moveshould either persist the newproject_id/pathor return a non-2xx error describing the failure. It must not report success when the operation was rolled back.- Independently, V2 should decode V1 messages that contain large inline-PDF
fileparts (or degrade gracefully, e.g. keep the text and drop/summarize the oversized attachment) instead of throwingMessageDecodeError, per the V1→V2 compatibility guarantee.
Actual Behavior
movereturns HTTP 200 / exit code 0 / empty body, but nothing is written. Verified immediately afterward viaGET /api/session/{id}:projectIDstillglobal.- Server log shows the swallowed failure at the moment of the move call:
(IDs truncated; they are opaque random identifiers.)
level=ERROR role=server message="Failed to drain Session" cause="Session.MessageDecodeError: Failed to decode message msg_e0a99b3de… in session ses_1f56df86…" level=ERROR role=server message="Failed to drain Session" cause="Session.MessageDecodeError: Failed to decode message msg_e0a5d6f2f… in session ses_1f5a290e…" - The offending part is structurally valid — keys
type,mime,filename,url,sourceall present, base64 well-formed (decodes to a valid%PDF-file). The only thing unusual is its size:urlstring length: 37,591,492 chars (data:application/pdf;base64,...)- decoded payload: 28,193,596 bytes (~28 MB), magic
%PDF-OK - whole part JSON: 37,591,709 bytes (~37.6 MB)
- Whole-DB scan: 21
fileparts total; only 2 sessions contain an inline-PDF part; 7 file parts have adata:URL > 1 MB. The 2 sessions with the largest inline PDFs are exactly the 2 that fail to move.
Additional Context
- Frequency: 100% reproducible for the 2 affected sessions; every
moveattempt returns success but leavesproject_id='global'. - Impact: The
moveendpoint is the recommended workaround for V1→V2 importer keeps project_id='global' for non-git directories, so migrated sessions never appear in the per-directory session picker #50551. Its silent failure makes that workaround unreliable for any session containing an undecodable message, with no error surfaced to the user, TUI, scripts, or integrations. I had to work around it by directly updatingsession_v2.project_id/pathin the DB (after a full backup) to replicate what a successfulmovedoes. - Redactions: PDF filename, the attachment's original on-disk path (in
source.path), the local username, and full session/message/part IDs were redacted or truncated. No file contents, base64 payloads, tokens, or keys are included — only sizes and the%PDF-magic check. - Suspected cause of defect 2: a size/length cap in the V2 message/part decoder or schema validation that a ~37.6 MB part exceeds. The base64 itself is valid, so this is a size limit, not a format mismatch.
- Related: V1→V2 importer keeps project_id='global' for non-git directories, so migrated sessions never appear in the per-directory session picker #50551 (non-git sessions stranded in
global; root reason themoveworkaround was needed here). Defect 1 (move returns 200 while rolling back) appears to be previously unreported.
Activity
opencode-agent commentedon Oct 13, 2026
Thanks for the detailed report. I reproduced both problems on v2.0.27, and they still happen on the latest
dev(c1615b1).What I did:
opencode api post /api/session/<id>/move --data '{"directory":"<dest>"}'.The command exited 0 with an empty body, but the session's
project_idand directory didn't change. Themoverequest stayed queued insession_inbox, and the server log showedFailed to drain SessionwithSession.MessageDecodeError(or the base64 regexSchemaErrorwhen the message hadn't been delivered to history yet). The script below reproduces it.From reading the code (I haven't tested a fix):
moveonly queues the move for the session runner and returns straight away. If the runner fails while loading history, the move is silently dropped.The decode failure itself looks like a known problem: the
Prompt.Base64regex breaks on multi-MB strings. That's tracked in #45558, with an open fix in #51272. Related: #55110 (same decode failure from a large attachment) and #49740 (drain failures are never reported to the client).Reproduction script (repro.sh)
Run it:
bash repro.shFinn763 commentedon Oct 21, 2026
I'd like to work on this — the move flow swallows the drain decode error and reports success. I'll surface the failure and keep the session unmoved.