You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ACP: slash commands execute but never stream their output #54371
Running as an ACP agent (in JetBrains Rider 2026.2): slash commands (built-in like /init or custom) execute, but their output is not visible in the chat.
Plain prompts in the same session work normally.
Another ACP agent in the same IDE does show its slash-command output, so the client's rendering path is fine.
At the protocol level
session/prompt replies end_turn immediately; no session/update follows for that turn.
20:44:23,425 → session/prompt id=5 {"prompt":[{"type":"text","text":"/my-test"}]}
20:44:23,457 ← {"stopReason":"end_turn","_meta":{}} ← 32 ms
… no ACP traffic for 4 m 16 s …
20:48:39,687 → session/set_config_option id=6
The command did run (it produced its effect).
OpenCode's session_message table shows that the turn ran to completion after it was closed on wire:
my-test was advertised in available_commands_update so this is the recognized-command path (not the unknown-/ fallthrough).
OpenCode version
2.0.26
Steps to reproduce
Install OpenCode as an ACP agent in Rider.
I used a custom agent path.
1.1. Install OpenCode independently from Rider.
1.2. Add OpenCode as a custom agent:
For command prompts the turn returns succeeded before Fiber.join(events), and then the release step closes the per-turn event subscription, so no session/update (and no permission.asked) can be emitted.
Possible fix
PR #49208 (open; probably needs rebase onto current v2).
Reacted by Petr Goroshkov, joaofasilva-kitar, Moritz, Uschi003, A. S. and Victor Lobato
Thanks for the clear report and the protocol log. I reproduced this on 2.0.26 with a simulated model and a config command my-test, driving opencode acp over stdio:
A plain prompt streams agent_message_chunk and usage_update, then returns end_turn (~1.6s).
/my-test returns end_turn after ~14ms, and no session/update arrives for that turn, even though the model request for the command is still made.
It still happens on the latest dev (7b3d4ce). The script below reproduces it. Your reading of the cause matches what I found: in packages/cli/src/acp/turn.ts, command prompts return before the turn's event stream is joined. #49208 targets this, and #53914 covers a related case where a command runs several cycles.
Reproduction script (acp-command.ts)
// Reproduces #54371: ACP session/prompt for a slash command returns end_turn// immediately and the command's model output is never streamed.// Usage: bun /workspace/repro/acp-command.ts (uses `opencode` from PATH)// OC_DEV=/path/to/opencode-checkout bun acp-command.ts (runs from source)importfsfrom"node:fs/promises"importosfrom"node:os"importpathfrom"node:path"constroot=awaitfs.mkdtemp(path.join(os.tmpdir(),"acp-cmd-"))consthome=path.join(root,"workspace")constxdg=(d: string)=>path.join(root,d)awaitfs.mkdir(home,{recursive: true})awaitfs.mkdir(path.join(xdg("config"),"opencode"),{recursive: true})constllmRequests: number[]=[]constllm=Bun.serve({hostname: "127.0.0.1",port: 0,asyncfetch(req){if(newURL(req.url).pathname!=="/v1/chat/completions")returnnewResponse("nf",{status: 404})awaitreq.json()llmRequests.push(Date.now())awaitBun.sleep(1500)// model takes a moment, like a real oneconstchunks=[{choices: [{delta: {role: "assistant"},finish_reason: null}],usage: null},{choices: [{delta: {content: "MODEL-REPLY-TEXT"},finish_reason: null}],usage: null},{choices: [{delta: {},finish_reason: "stop"}],usage: null},{choices: [],usage: {prompt_tokens: 10,completion_tokens: 1,total_tokens: 11}},]returnnewResponse(chunks.map((c)=>`data: ${JSON.stringify(c)}\n\n`).join("")+"data: [DONE]\n\n",{headers: {"content-type": "text/event-stream"},})},})constmodel={name: "Test Model",capabilities: {tools: true,input: ["text"],output: ["text"]},cost: {input: 0,output: 0},limit: {context: 100000,output: 10000},}awaitBun.write(path.join(xdg("config"),"opencode","opencode.json"),JSON.stringify({update: "disable",model: "test/test-model",providers: {test: {name: "Test",package: "aisdk:@ai-sdk/openai-compatible",settings: {apiKey: "x",baseURL: `http://127.0.0.1:${llm.port}/v1`},models: {"test-model": model},},},commands: {"my-test": {template: "Say hello",description: "test command"}},}),)constmodels=path.join(root,"models.json")awaitBun.write(models,"{}")constdev=process.env.OC_DEVconstcmd=dev
? ["bun","run","src/index.ts","acp"]// run from <checkout>/packages/cli, like the repo's ACP subprocess tests
: ["opencode","acp"]constchild=Bun.spawn(cmd,{cwd: dev ? path.join(dev,"packages/cli") : home,stdin: "pipe",stdout: "pipe",stderr: "pipe",env: {
...process.env,HOME: root,XDG_CONFIG_HOME: xdg("config"),XDG_DATA_HOME: xdg("data"),XDG_STATE_HOME: xdg("state"),XDG_CACHE_HOME: xdg("cache"),OPENCODE_DISABLE_AUTOUPDATE: "true",OPENCODE_MODELS_PATH: models,},})constt0=Date.now()constlog=(dir: string,m: any)=>{consts=JSON.stringify(m)console.log(`+${String(Date.now()-t0).padStart(6)}ms ${dir}${s.length>220 ? s.slice(0,220)+"…" : s}`)}constpending=newMap<number,(m: any)=>void>()constupdates: {at: number;m: any}[]=[];(async()=>{constdec=newTextDecoder()letbuf=""forawait(constchunkofchild.stdout){buf+=dec.decode(chunk)letiwhile((i=buf.indexOf("\n"))>=0){constline=buf.slice(0,i).trim()buf=buf.slice(i+1)if(!line)continueconstm=JSON.parse(line)if(m.method==="session/update")updates.push({at: Date.now(), m })if(m.method!=="session/update"||m.params?.update?.sessionUpdate!=="available_commands_update")log("<-",m)if(m.id!==undefined&&!m.method)pending.get(m.id)?.(m)}}})()letid=0constrequest=(method: string,params: any)=>{constmsg={jsonrpc: "2.0",id: ++id, method, params }log("->",msg)child.stdin.write(JSON.stringify(msg)+"\n")child.stdin.flush()returnnewPromise<any>((r)=>pending.set(msg.id,r))}awaitrequest("initialize",{protocolVersion: 1,clientCapabilities: {},clientInfo: {name: "repro",version: "0"}})consts=awaitrequest("session/new",{cwd: home,mcpServers: []})constsessionId=s.result.sessionIdawaitBun.sleep(1000)asyncfunctionturn(text: string){constbefore=updates.lengthconstsent=Date.now()constres=awaitrequest("session/prompt",{ sessionId,prompt: [{type: "text", text }]})constreplyAt=Date.now()awaitBun.sleep(5000)// wait for anything lateconstduring=updates.slice(before).filter((u)=>u.at<=replyAt)constchunks=during.filter((u)=>u.m.params.update.sessionUpdate==="agent_message_chunk")console.log(`\n== "${text}": reply ${res.result?.stopReason??JSON.stringify(res.error)} after ${replyAt-sent}ms; `+`agent_message_chunk updates before reply: ${chunks.length}; total updates after send: ${updates.length-before}\n`,)}awaitturn("plain prompt")awaitturn("/my-test")console.log(`LLM requests received: ${llmRequests.length}`)child.kill()llm.stop(true)process.exit(0)
Still happens on 2.2.5 (Linux, opencode acp driven by OpenHands Agent Canvas 1.34, and by a plain stdio script). A project command whose body runs sleep 20; echo pong gets end_turn at once, with no session/update for the turn, while its shell call has already started in the session. Built-in /compact only looks fine because it has nothing to stream: it returns end_turn in about a second and the compaction is there. Plain prompts stream normally.
Still happens on 2.2.5 (macOS arm64, the latest release). The ACP client is Orca 1.4.250, running opencode acp over stdio.
I sent a custom slash command, /implement-verify <args>, whose body is "Load the implement-verify skill and follow it for this task: $ARGUMENTS". session/prompt returned end_turn after 13 ms. Orca's journal shows the turn starting and ending 13 ms apart with outcome: success. According to OpenCode's own history, the command kept running for about 84 s, and no session/update reached the client. The user sees an empty turn labelled "Worked for 0s".
The same client also gets nothing for a turn the server resumes after a background shell finishes. That is the case nbrumont described in #55760. In the 2.2.5 binary, the ACP layer opens one event subscription per prompt and closes it when the prompt returns, so nothing is forwarded afterwards.
A fix would wait for the turn's terminal event on the command path, and keep one subscription per attached session instead of one per prompt.
Still reproduces on 2.2.7 (macOS, arm64), driving opencode acp over stdio with OPENCODE_CLIENT=acp, an isolated HOME/XDG_*, a fresh session and model opencode/big-pickle:
Plain prompt (Reply with the single word pong.): agent_message_chunk + usage_update, then end_turn about 4.3 s after session/prompt.
/review the uncommitted change: {"stopReason":"end_turn"}9 ms after session/prompt, then no session/update for the next 60 s.
session_message for that session: an expanded reviewer user message created right after the response, four assistant messages over about 40 s, then idle outcome=succeeded.
A later session/load of the same session replays the full turn (1 user chunk, 4 thought chunks, 6 tool calls + 6 tool-call updates, 1 agent message chunk).
packages/cli/src/acp/turn.ts on dev still has if (prompt.command) return "succeeded" as const before Fiber.join(events).
Still unfixed.
I am having issues with my Orca integration and early terminations of my prompts because of it, so I've opened a PR #63097 that fixes that
hopefully someone can take a look and merge it. I am running my fix on a patched version of OpenCode locally and it's working flawlessly, I hope this can be merged to main soon.
Activity
opencode-agent commentedon Oct 10, 2026
Thanks for the clear report and the protocol log. I reproduced this on 2.0.26 with a simulated model and a config command
my-test, drivingopencode acpover stdio:agent_message_chunkandusage_update, then returnsend_turn(~1.6s)./my-testreturnsend_turnafter ~14ms, and nosession/updatearrives for that turn, even though the model request for the command is still made.It still happens on the latest
dev(7b3d4ce). The script below reproduces it. Your reading of the cause matches what I found: inpackages/cli/src/acp/turn.ts, command prompts return before the turn's event stream is joined. #49208 targets this, and #53914 covers a related case where a command runs several cycles.Reproduction script (acp-command.ts)
Run it:
bun acp-command.tsgerman-gd commentedon Nov 3, 2026
Still happens on 2.2.5 (Linux,
opencode acpdriven by OpenHands Agent Canvas 1.34, and by a plain stdio script). A project command whose body runssleep 20; echo ponggetsend_turnat once, with nosession/updatefor the turn, while its shell call has already started in the session. Built-in/compactonly looks fine because it has nothing to stream: it returnsend_turnin about a second and the compaction is there. Plain prompts stream normally.tazeverywhere commentedon Nov 3, 2026
Confirming this on 2.2.5 with Zed 1.26.2 on macOS 26.7.2 (Darwin 25.6.0, arm64), so it is not specific to Rider/OpenHands.
Setup: Zed custom agent, no extension:
Reproduction:
.opencode/commands/test.md(body:Run the shell command: echo pong)./test.Notes:
prompt.commandearly-return described above.available_commands_update(it autocompletes in the panel), so this is the recognized-command path.Happy to test a build that includes the fix if it helps.
sbalyakin commentedon Nov 6, 2026
Still happens on 2.2.5 (macOS arm64, the latest release). The ACP client is Orca 1.4.250, running
opencode acpover stdio.I sent a custom slash command,
/implement-verify <args>, whose body is "Load theimplement-verifyskill and follow it for this task: $ARGUMENTS".session/promptreturnedend_turnafter 13 ms. Orca's journal shows the turn starting and ending 13 ms apart withoutcome: success. According to OpenCode's own history, the command kept running for about 84 s, and nosession/updatereached the client. The user sees an empty turn labelled "Worked for 0s".The same client also gets nothing for a turn the server resumes after a background shell finishes. That is the case nbrumont described in #55760. In the 2.2.5 binary, the ACP layer opens one event subscription per prompt and closes it when the prompt returns, so nothing is forwarded afterwards.
A fix would wait for the turn's terminal event on the command path, and keep one subscription per attached session instead of one per prompt.
abdoulayeYATERA commentedon Nov 7, 2026
Still no fix here ?
fix(acp): stream a command's expanded prompt
AmoabaKelvin commentedon Nov 11, 2026
Still reproduces on 2.2.7 (macOS, arm64), driving
opencode acpover stdio withOPENCODE_CLIENT=acp, an isolatedHOME/XDG_*, a fresh session and modelopencode/big-pickle:Reply with the single word pong.):agent_message_chunk+usage_update, thenend_turnabout 4.3 s aftersession/prompt./review the uncommitted change:{"stopReason":"end_turn"}9 ms aftersession/prompt, then nosession/updatefor the next 60 s.session_messagefor that session: an expanded reviewerusermessage created right after the response, fourassistantmessages over about 40 s, thenidle outcome=succeeded.session/loadof the same session replays the full turn (1 user chunk, 4 thought chunks, 6 tool calls + 6 tool-call updates, 1 agent message chunk).packages/cli/src/acp/turn.tsondevstill hasif (prompt.command) return "succeeded" as constbeforeFiber.join(events).paulovlobato commentedon Nov 11, 2026
Still unfixed.
I am having issues with my Orca integration and early terminations of my prompts because of it, so I've opened a PR #63097 that fixes that
hopefully someone can take a look and merge it. I am running my fix on a patched version of OpenCode locally and it's working flawlessly, I hope this can be merged to main soon.
fix(acp): stream a command's expanded prompt