Repository navigation
Description
opencode run fails against openai-compatible provider: stream always ends without finish_reason
Summary
opencode run fails 100% of the time against a custom openai-compatible provider (Z.AI coding endpoint via @ai-sdk/openai-compatible): every streaming response terminates without a finish_reason and the session aborts after retries — even for a trivial prompt. Interactive TUI sessions against the same provider, background service, and config work flawlessly, so the provider is reachable and the credential is valid.
Environment
- opencode version: 2.0.27
- OS: Windows 11, build 10.0.26300.9457 (win32 x64)
- Terminal: Unavailable: reproduced via the shared background service (non-interactive
opencode run); interactive sessions on the same machine work - Shell:
cmd.exe(COMSPEC); reproduction invoked through OpenCode's shell tool (git-bash) - Install/channel: latest
- Active plugins: none
Provider config (key redacted):
"provider": {
"provider-auth-zai": {
"name": "Z.AI",
"npm": "@ai-sdk/openai-compatible",
"options": {
"apiKey": "{redacted}",
"baseURL": "https://api.z.ai/api/coding/v1"
}
}
}Models tried: glm-5 and glm-4.7 — both affected.
Reproduction
- Configure an openai-compatible provider pointing at the Z.AI coding endpoint (
https://api.z.ai/api/coding/v1) with@ai-sdk/openai-compatible. - With the background service running (during these reproductions another interactive session was active on the same service — possibly relevant, see concurrency note below).
- Run:
opencode run "Reply with exactly: OK"Also reproduced with --model provider-auth-zai/glm-4.7 and with a longer prompt requiring MCP tool use. 4/4 failing sessions.
Expected Behavior
The run session completes and prints OK — the same provider streams normally in interactive sessions on this machine and config.
Actual Behavior
The CLI retries the step (4–10 times, shown as > build · glm-5 lines), then exits with:
Error: OpenAI Chat stream ended without finish_reason
Service log entry (identical across all four sessions):
level=ERROR message="Failed to drain Session"
cause="AI.Error: OpenAI Chat stream ended without finish_reason
... SessionStep.attempt ... SessionRunner.runStep ... SessionRunner.drain ..."
{ [cause]: AI.Error.InvalidProviderOutput: OpenAI Chat stream ended without finish_reason }
Failing session IDs: ses_ecaedfc7fffehVBgQO1KLszcZT, ses_ecaec6b48ffe8ISDa8d4M3807d, ses_ecaeadf2bffeJw8m78Tf3wzY5i, ses_ecae9683dffek5esLzAgzYJQq5i
Additional Context
- 100% reproducible in
runmode (4 sessions, 2 models, trivial and long prompts); 0% in interactive sessions on the same provider/service. - Ruled out: prompt content, model choice, MCP servers (failure occurs before any tool access; reproduced with a trivial prompt), and provider availability (interactive sessions stream fine concurrently).
- Two candidate causes worth discriminating:
- Concurrency cap on the provider plan — every failure occurred while another OpenCode session was active on the same API key. The Z.AI SSE stream may be dropped for concurrent non-interactive requests.
- Run-mode request composition — something in how
runbuilds the request (agent/system prompt assembly, tool schemas, streaming options) may cause the provider to truncate the stream.
- Workaround: use interactive sessions;
opencode runis unusable with this provider.
Activity
opencode-agent commentedon Oct 13, 2026
Thanks for the detailed report. I tried this on v2.0.27 with your provider config, pointed at a local mock openai-compatible server instead of Z.AI. With a normal stream that ends in
finish_reason: "stop",opencode run "Reply with exactly: OK"printedOKand exited cleanly. Sorunhandles a well-formed stream, and nothing in the request it sent looked specific torunmode.That suggests the problem depends on what Z.AI's coding endpoint actually returns to these requests. I can't check that here because it needs a real Z.AI account, so this needs a maintainer who has one.
If you can, these would help narrow it down:
opencode runstill fail when no other OpenCode session is active on the same API key? That would tell a concurrency limit apart from something about the request itself.cmd.exe/PowerShell instead of through OpenCode's shell tool, without the shared background service?Related, though it looks like a different trigger: #50146 (the same
stream ended without finish_reasonerror, intermittent on GLM streams via OpenCode Go).Israel-Kli commentedon Oct 14, 2026
I can reproduce the same missing
finish_reasonfailure with Snowflake Cortex's OpenAI-compatible endpoint, specifically for Claude models.Environment:
snowflake-cortex,@ai-sdk/openai-compatiblehttps://<account>.snowflakecomputing.com/api/v2/cortex/v1/chat/completionsclaude-sonnet-4-6,claude-opus-5,openai-gpt-5.2Session DB evidence from opencode after a successful visible answer from
claude-sonnet-4-6:That makes opencode inject the synthetic continuation prompt:
The direct SSE response explains it. Cortex's Claude stream emits content, then an empty role chunk, then usage, then
[DONE]. No chunk containschoices[0].finish_reason:By comparison, the same Cortex OpenAI-compatible endpoint with
openai-gpt-5.2does include the terminal finish chunk:Cortex's Anthropic-style endpoint for the same Claude model does provide an explicit stop signal:
So this is another OpenAI-compatible provider/model combination where a completed stream can end cleanly with
[DONE]but withoutfinish_reason. opencode currently treats it as a failed turn and retries, even though the answer was already delivered.