Repository navigation
Description
Summary
opencode session export <session-id> produces complete, valid JSON when stdout is a regular file, but a truncated prefix when stdout is a pipe — while still exiting 0. Every transcript larger than the OS pipe buffer is silently cut off, so opencode session export X | jq, or capturing the export into memory, gets corrupted JSON with no diagnostic.
Environment
- opencode version: 2.0.32
- OS: Darwin 25.6.0 arm64 (
Darwin Kernel Version 25.6.0: Fri Jul 31 19:19:08 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T6050) - Terminal:
TERM=xterm-256color,TERM_PROGRAM=vscode,COLORTERM=truecolor(CLI invoked non-interactively from a shell) - Shell:
/bin/zsh - Install/channel: latest, release — Homebrew
/opt/homebrew/bin/opencode→@opencode/cli2.0.32 - Active plugins: none configured (no
pluginskey in~/.config/opencode/opencode.json, no project.opencode/directory)
Reproduction
Session ids below are my own and omitted; any session whose export exceeds the pipe buffer behaves the same.
- Pick a real session whose export is large (this one is 1,195,916 bytes).
- Redirect stdout to a file:
opencode session export <id> > full.json→ 1,195,916 bytes,json.loadssucceeds, exit 0. - Pipe stdout through
cat:opencode session export <id> | cat > piped.json→ 327,680 bytes, invalid JSON, exit 0. - Pipe stdout to
wc:opencode session export <id> | wc -c→ 262,144 bytes. - Capture stdout with Python
subprocess.stdout=PIPE→ 262,144 bytes, exit 0,json.loadsfails withUnterminated string starting at: line 2121 column 25 (char 258002). - Control, a small session (~806 bytes): pipe and file-redirect results are byte-for-byte identical.
import subprocess, json
sid = "<a-session-with-a-1.2MB-export>"
r = subprocess.run(["opencode", "session", "export", sid],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(len(r.stdout), r.returncode) # 262144 0
json.loads(r.stdout) # ValueError: Unterminated string starting at ...Expected Behavior
Byte-identical output regardless of whether stdout is a file, a pipe, or captured in memory. If the write genuinely cannot be completed, a non-zero exit and a message on stderr — never a partial payload reported as success.
Actual Behavior
Truncated output with exit status 0. The truncated stream is a strict prefix of the full payload (full.startswith(truncated) is true), so bytes are simply missing from the end, and the amount lost varies between runs of the same command (262,144 vs 327,680 bytes of the same 1,195,916-byte export). That smells like the process exiting before pending stdout writes are flushed, rather than a deliberate size limit.
Additional Context
- Impact. I archive every session nightly for durable history. The first version of my sweeper captured stdout with
PIPE; 27 of 51 sessions came back as invalid JSON, cut mid-string around character 261k. 10 of my 51 archived sessions exceed 1 MB, so this is the common case, not an edge case. Anyone piping intojqor an editor buffer gets a half a transcript and no warning. - Silent is the worst part. Exit 0 plus a valid-looking prefix means callers cannot detect the failure after the fact; my archive would have been quietly incomplete had I not validated each payload.
- Workaround. Redirect stdout to a file instead of piping it — that path is always correct. Validate the payload before using it.
- Frequency. Consistent: reproduced on every attempt (4/4) across three different sessions; only the byte count that survives varies.
- Separate issue. A different session in the same store fails to export entirely with
Unexpected server error. Check server logs for details.(exit 1, ~18 MB stored). I will file that separately since it looks like a distinct defect.
Activity
opencode-agent commentedon Oct 20, 2026
Thanks for the detailed report! I reproduced this on 2.0.32 (on Linux, so it isn't specific to macOS), and it still happens on the latest
dev(16c338372).What I did: I imported a session of about 1.2 MB, then exported it three ways.
cat: 589,824 or 720,896 bytes depending on the run, invalid JSON.Every run exited 0, and the truncated sizes are multiples of 4096. The script below reproduces it.
The likely cause: the export handler writes its JSON to stdout without waiting for the write to finish, and the CLI then exits, which drops whatever is still queued for the pipe. #52925 is the same problem in
opencode api get; its open fix (#52955) doesn't coversession export.Until this is fixed, redirecting to a file (as you're doing) gives the full export.
Reproduction script (repro.sh)
Run it:
bash repro.shAlek2077 commentedon Nov 3, 2026
Still reproducible on OpenCode 2.2.5 (Linux x86_64, WSL2).
opencode session export <id> > full.json→ 2,972,714 bytes, valid JSON, exit 0.opencode session export <id> | wc -c→ 262,144 bytes, exit 0;jqfails withUnterminated string.Redirecting to a file is the workaround. This matches the flush-before-exit diagnosis; #57347 looks like the right fix.