Repository navigation
Description
Summary
In OpenCode V2, the public event schema declares session.idle (and session.status, session.error), but the server never publishes those events on the event stream. Runtime completion is emitted only as session.execution.succeeded / session.execution.failed / session.execution.interrupted. Any plugin that subscribes to session.idle (the documented V1 → V2 migration example uses exactly this) silently never fires.
Environment
- opencode version: 2.0.28
- OS: Linux 7.0.0-38-generic (Ubuntu, x64)
- Terminal: TERM=xterm-256color, COLORTERM=truecolor
- Shell: /bin/bash
- Install/channel: latest (channel=latest, local=false)
- Active plugins:
cost-loggerandnotifyfrom~/.config/opencode/plugins/(local files); nopluginsentries in config
Reproduction
-
Create a local plugin at
~/.config/opencode/plugins/probe.jsthat logs every event it receives:import fs from "node:fs" export default { id: "probe", setup: async (ctx) => { const ac = new AbortController() void (async () => { for await (const e of ctx.event.subscribe({ signal: ac.signal })) { fs.appendFileSync("/tmp/probe.log", e.type + "\n") } })() return () => ac.abort() }, }
-
Run
opencode reload, then trigger a normal session, e.g.opencode run --auto "say hi". -
Inspect
/tmp/probe.logforsession.idle.
The same result occurs when the session is driven from the TUI (interactive), not only via run.
Expected Behavior
session.idle should be delivered to ctx.event.subscribe() subscribers when a session becomes idle, matching the schema and the V1 → V2 migration guidance, which shows if (event.type === "session.idle") console.log("Session completed").
Actual Behavior
session.idle is never delivered. Over multiple runs, the events actually received are:
session.created
session.inbox.enqueued
session.inbox.delivered
session.instructions.updated
session.text.started / .delta / .ended
session.step.started / .streamed / .ended
session.usage.updated
session.execution.started
session.execution.succeeded <-- completion
session.renamed
For a failed run, completion arrives as session.execution.failed (data carries the error). Neither session.idle, session.status, nor session.error appears.
Binary inspection of the installed build is consistent: session.idle appears only once as a schema/type declaration with no publication site, while session.execution.{started,succeeded,failed,interrupted} are wired into the event flow.
Additional Context
- Impact: V1-style plugin logic does not port cleanly. The migration guide's own event example (
session.idle) is a no-op in V2. Plugins that react to "session finished" (notifications, cost/usage syncing, summaries) never trigger. - Workaround: subscribe to
session.execution.succeeded/session.execution.failed/session.execution.interruptedinstead. - The
@opencode-ai/sdkpublished types still containEventSessionIdle/SessionIdle, so the schema is not the problem — the missing runtime emission is. - Environment note: the event stream is authenticated (
GET /api/eventreturns401without credentials), so rawcurlcannot be used to observe it;ctx.event.subscribe()from a plugin works.
Related but distinct: #55227 (/wait returns when execution starts, not when the session is idle) and #51507 (completion notifications). This report is specifically about session.idle being declared in the V2 event schema but never emitted.
Activity
opencode-agent commentedon Oct 14, 2026
Thanks for the detailed report. I was able to reproduce this on 2.0.28.
I loaded your probe plugin (
ctx.event.subscribe(), logging eache.type) in an isolated project and submitted one prompt in the TUI, with a simulated model replying. The plugin received 25 event types, includingsession.execution.startedandsession.execution.succeeded. It never receivedsession.idleorsession.status.The latest
dev(34d8ded) gives the same result. In the code, both events are declared in the V2 event schema, but I couldn't find any place that publishes them. The V2 migration guide still usessession.idleas its example.The script below reproduces it. Its log output shows which events arrive, so the recording only shows the TUI run itself. Until this is resolved, subscribing to
session.execution.succeeded/failed/interruptedworks, as you found.Related: #55227 (
/waitreturns when execution starts, not when the session is idle).recording.mp4
Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.holny commentedon Oct 14, 2026
I'd like to take this. Confirmed the diagnosis —
session.idleis markeddeprecatedinpackages/schema/src/session-status-event.ts, and its replacementsession.statusis declared but never published anywhere in core/server. The natural emit points aresession.execution.started(statusbusy) andsucceeded/failed/interrupted(statusidle) inpackages/core/src/session/execution.ts. I'll wire those up, add a test asserting thesession.statuspayloads land on the bus, and base againstorigin/dev.OpenCode: a turn that ends is reported idle, from session.execution.s…
OpenCode: a turn that ends is reported idle, from session.execution.s…