Description
When running opencode run --format json (or crush run --format json), the stdout stream only outputs the step_start event and silently drops all subsequent events (text, reasoning, step_finish). The AI model actually generates a complete response (confirmed in the SQLite database), but the JSON-formatted stdout stream is truncated after the first event.
This makes --format json mode completely unusable for programmatic consumers that rely on parsing stdout to capture AI responses.
Steps to Reproduce
-
Run any simple prompt with --format json:
opencode run "say hello" --format json --dangerously-skip-permissions --dir /tmp --model deepseek/deepseek-v4-pro
-
Observe the stdout output — it only contains:
{"type":"step_start","timestamp":1780013193618,"sessionID":"ses_18ef22a63ffeYvPBilm6eD8TSy","part":{"id":"prt_e710dd98d001MXtxfuunJqXc8F","messageID":"msg_e710dd6e60012YH30gCmnLQtVv","sessionID":"ses_18ef22a63ffeYvPBilm6eD8TSy","type":"step-start"}}
No text, reasoning, or step_finish events follow. The process exits with code 0 and empty stderr.
-
Verify the AI response is actually complete in the SQLite database:
sqlite3 ~/.local/share/opencode/opencode.db \
"SELECT json_extract(p.data, $.type), substr(json_extract(p.data, $.text), 1, 80)
FROM part p WHERE p.message_id IN (
SELECT id FROM message WHERE session_id = ses_18ef22a63ffeYvPBilm6eD8TSy
AND json_extract(data, $.role) = assistant
) ORDER BY p.time_created"
Output:
step-start|
reasoning|The user is asking me to say hello. This is a very simple conversational request
text|Hello
step-finish|
-
The AI did generate "Hello" and it was saved to the database, but it was never written to stdout.
Reproducibility
This bug is highly reproducible (~80-100% failure rate). I ran 5 consecutive tests:
for i in 1 2 3 4 5; do
opencode run "say number $i" --format json --dangerously-skip-permissions --dir /tmp --model deepseek/deepseek-v4-pro
echo "---"
done
Result: All 5 runs only output step_start. In a separate batch of 5 runs, 1 run succeeded (output both step_start + text), suggesting this is a race condition in the stdout writer — the process exits before the text/reasoning/step_finish events are flushed to stdout.
Expected Behavior
--format json should output all events to stdout in order:
{"type":"step_start",...}
{"type":"reasoning",...}
{"type":"text","part":{"type":"text","text":"Hello",...}}
{"type":"step_finish",...}
Actual Behavior
Only step_start is written to stdout. The remaining events are lost, even though they are persisted in the internal SQLite database.
Root Cause Analysis
Based on the internal debug logs (via --print-logs --log-level DEBUG), the event bus correctly processes all events:
INFO service=bus type=message.part.delta publishing
INFO service=bus type=message.part.updated publishing
INFO service=bus type=message.updated publishing
The session.prompt loop exits immediately after step 1:
INFO service=session.prompt session.id=ses_xxx step=1 loop
INFO service=session.prompt session.id=ses_xxx exiting loop
This strongly suggests the --format json stdout writer subscribes to the event bus for step_start but the process exits (or the writer unsubscribes) before text/reasoning/step_finish events are emitted to stdout. The database writer persists events asynchronously and completes successfully, but the stdout stream is closed too early.
Workaround
Consumers can read the response from the OpenCode/Crush SQLite database at ~/.local/share/opencode/opencode.db (or equivalent) by querying the part table with the session ID. However, this is fragile and not documented.
Version
- OpenCode CLI: 1.15.12 (also reproduced on 1.15.10)
- Also applies to
crush run --format json based on shared codebase
Environment
- OS: Linux 6.17.0-23-generic x86_64 (Ubuntu)
- Shell: bash
- Model provider: DeepSeek (deepseek-v4-pro)
- Install method: npm (
opencode-ai package)
Additional Context
This bug was discovered while integrating OpenCode as a backend in ClawBench, a multi-AI-backend chat platform. We parse opencode run --format json stdout to stream AI responses to users, and this bug causes all responses after the first message to appear as "AI returned no content".
Related issue: #2412 (mentions --format json headless mode event structure, confirming programmatic consumers rely on this feature).
Description
When running
opencode run --format json(orcrush run --format json), the stdout stream only outputs thestep_startevent and silently drops all subsequent events (text,reasoning,step_finish). The AI model actually generates a complete response (confirmed in the SQLite database), but the JSON-formatted stdout stream is truncated after the first event.This makes
--format jsonmode completely unusable for programmatic consumers that rely on parsing stdout to capture AI responses.Steps to Reproduce
Run any simple prompt with
--format json:opencode run "say hello" --format json --dangerously-skip-permissions --dir /tmp --model deepseek/deepseek-v4-proObserve the stdout output — it only contains:
{"type":"step_start","timestamp":1780013193618,"sessionID":"ses_18ef22a63ffeYvPBilm6eD8TSy","part":{"id":"prt_e710dd98d001MXtxfuunJqXc8F","messageID":"msg_e710dd6e60012YH30gCmnLQtVv","sessionID":"ses_18ef22a63ffeYvPBilm6eD8TSy","type":"step-start"}}No
text,reasoning, orstep_finishevents follow. The process exits with code 0 and empty stderr.Verify the AI response is actually complete in the SQLite database:
Output:
The AI did generate "Hello" and it was saved to the database, but it was never written to stdout.
Reproducibility
This bug is highly reproducible (~80-100% failure rate). I ran 5 consecutive tests:
Result: All 5 runs only output
step_start. In a separate batch of 5 runs, 1 run succeeded (output bothstep_start+text), suggesting this is a race condition in the stdout writer — the process exits before the text/reasoning/step_finish events are flushed to stdout.Expected Behavior
--format jsonshould output all events to stdout in order:{"type":"step_start",...} {"type":"reasoning",...} {"type":"text","part":{"type":"text","text":"Hello",...}} {"type":"step_finish",...}Actual Behavior
Only
step_startis written to stdout. The remaining events are lost, even though they are persisted in the internal SQLite database.Root Cause Analysis
Based on the internal debug logs (via
--print-logs --log-level DEBUG), the event bus correctly processes all events:The
session.promptloop exits immediately after step 1:This strongly suggests the
--format jsonstdout writer subscribes to the event bus forstep_startbut the process exits (or the writer unsubscribes) beforetext/reasoning/step_finishevents are emitted to stdout. The database writer persists events asynchronously and completes successfully, but the stdout stream is closed too early.Workaround
Consumers can read the response from the OpenCode/Crush SQLite database at
~/.local/share/opencode/opencode.db(or equivalent) by querying theparttable with the session ID. However, this is fragile and not documented.Version
crush run --format jsonbased on shared codebaseEnvironment
opencode-aipackage)Additional Context
This bug was discovered while integrating OpenCode as a backend in ClawBench, a multi-AI-backend chat platform. We parse
opencode run --format jsonstdout to stream AI responses to users, and this bug causes all responses after the first message to appear as "AI returned no content".Related issue: #2412 (mentions
--format jsonheadless mode event structure, confirming programmatic consumers rely on this feature).