Description
Description
Summary
Background subagents complete their turn (the final report is persisted and
visible in the UI and in the local SQLite store), but the parent session never
receives the completion notification for that turn. The orchestrating agent
waits indefinitely unless the human relays the result manually. Observed three
times in one working session, all on sessions that had been resumed via the
prompt-session tool; initial background completions of freshly spawned
subagents were delivered correctly every time
Expected Behavior
When a background child session finishes a turn, the parent session receives
the completion notification (the <subagent state="completed"> message with
the final report), as it does for the child's first background completion.
Actual Behavior
The turn completes silently from the parent's point of view. No error is
surfaced anywhere; the report is only recoverable from the local database or
by the human user relaying it.
Additional Context
- Workaround: read the child's last
assistantmessage from
~/.local/share/opencode/opencode.db(session_messagetable, order by
seqdescending), or have the human relay it. - Possibly related but distinct: Sub-agent Task silently returns empty result when the final response stream fails (e.g. 503) — no error, no retry, completed work lost #40527 (empty result when the final stream
fails — here the final message exists and is persisted), Interrupted subagents lose completed work/context and return empty results to parent #29209 (results
lost after interruption — here nothing interrupts the child), Tasks/Subagents with Codex / OpenAI are frequently getting stuck with no timeout/retry, which then hangs the session forever #11865 (hang
with no timeout — here the child does not hang). - Happy to run instrumented builds or collect specific logs on request.
Plugins
opencode-froggy
OpenCode version
2.0.14
Steps to reproduce
- From a primary session, spawn a background subagent with the
subagenttool
(agentgeneral; models used:opencode-go/deepseek-v4.1-flash#maxand
opencode/mimo-v2.6-flash-free). The completion notification for this first
turn arrives normally. - Later, send the same child session a follow-up instruction via the
prompt-sessiontool (e.g. "apply these review fixes and report"). - The child session processes the instruction and finishes its turn: the final
assistant message exists in~/.local/share/opencode/opencode.db
(tablesession_message), and in one verified case carries
finish: "stop"followed by a terminalidleevent with
outcome: "succeeded". - No completion notification is ever delivered to the parent session. The
parent only learns the result when the human says "it finished" and reads
the message out of the SQLite store.
Frequency: three occurrences within ~6 hours on 2026-09-22/23, across three
different child sessions (two general agents on different models, one
code-reviewer). A server restart occurred earlier in the same parent session
(before the observed misses); unknown whether it is a contributing factor.
Screenshot and/or share link
No response
Operating System
Linux 7.1.13-402.asahi.fc44.aarch64+16k (Fedora Asahi, arm64)
Terminal
Ghostty
Activity
github-actions commentedon Sep 22, 2026
This issue might be a duplicate of existing issues. Please check:
prompt-sessionafter the first background turn, which may help pinpoint whether this is the same underlying fix or a distinct regression.opencode-agent commentedon Sep 24, 2026
Thanks for the detailed report. The follow-up in this repro uses
opencode-froggy'sprompt-sessiontool, rather than V2's built-insubagenttool. In V2, the built-in tool registers a background job and completion notification when invoked withbackground: true, including when continuing an existing child withsessionID. Calling the session prompt API directly admits/runs the child's next turn, but does not register a new parent completion notification.At the Froggy revision preceding this change,
prompt-sessionsimply calledsession.promptand returned. That explains why the initial native subagent turn notified the parent while later plugin-prompted turns completed without notifying it. This isn't evidence of a V2 native subagent notification regression, and the child doesn't need to be running background processes to produce it. The linked Froggy change adds a plugin-side completion watcher.Could you retry with the Froggy update containing that change, or continue the child with the native
subagenttool (sessionIDplusbackground: true)? If the native continuation misses a completion notification, a repro using that path would be useful to investigate on the V2 side.Codestz commentedon Sep 25, 2026
Same gap in the foreground case, measured on 1.18.32 and 2.0.15. A subagent finishes and returns its result to the parent. The user then messages the child session directly (v1
session.promptAsync, v2session.prompt): the child wakes, does the work, and answers. The parent never hears about it: no notification, no new turn, and nothing when it's next prompted. Messages sent while the child is still running are fine, because they land in the answer the parent receives.For opencode-cockpit (a subagents viewer) I work around it by adding the exchange to the parent without starting a turn (v1
noReply: true, v2session.syntheticwithresume: false). A built-in<subagent>notification for any completed child turn would cover both this and the resumed-background case above.0x4nur4g commentedon Sep 25, 2026
Corroborating this at scale — same mechanism, 44 instances, with a controlled reproduction.
Loss signature. Scanning the local session store for "child terminal >60 s after its last delivered
<subagent>notification" finds 44 instances: 2 on 2026-09-10 (beta-19425), 1 on 09-23, 23 on 09-24, 18 on 09-25. Every loss sits on a child that had been resumed or re-woken; fresh-spawn first completions were delivered correctly every time, matching the report.One fully-traced instance. Parent resumed a background child at 17:08; the drain's first idle fired the notification at 17:42:58. A second call from the parent at 17:41:10 (admitted while the child was busy) promoted as a successor turn after that idle. Final idle: 17:45:25 — no notification, ever. Parent had gone idle at 17:44:56; its inbox was empty and pending count 0 (never posted, not queued).
Controls. No service restart in the window (single run id across the log). Error drains still notify — a child whose drain failed on 2026-09-25 delivered two notifications. Empty inbox rules out queue-promotion loss.
Controlled reproduction. Spawned a test orchestrator-type agent as a subagent with one background child. Its first idle landed 2 s before the child's completion notification; the parent received the interim text as final. The child's real verdict turn (26 s later) was invisible by construction, recovered only via
GET /api/session/{id}/messagereadback.Mechanism (hypothesis consistent with all observations): the background job's watcher fires at the child's first idle after the parent's call and is consumed. Any later drain — steer/queue-promoted successor, or a wake from a
<shell state="completed">synthetic — is owned by no job, so its terminal event injects nothing. The delivered notification is additionally prematurely terminal: the parent is toldcompletedat an intermediate idle while the real final state lands later, unseen.Exposure note. Local loss rate went ~2 per 2 weeks → 41 in 2 days when the fleet moved all lead→worker dispatch to background on 09-24. Can share a sanitized per-instance table — timestamps, opaque session IDs, and outcome fields only; no paths, project names, or transcripts — plus the SQL signature behind the scan, if useful for triage.
johnmwhitman commentedon Sep 27, 2026
The 44-instance trace suggests the invariant needs to be per continuation turn, not per child session or first idle. Concretely, each parent-to-child continuation could create a durable delivery obligation keyed by
(parentSessionID, childSessionID, turnID/finalMessageID). Mark it delivered only when that exact terminal assistant message is injected into the parent; on parent resume, replay any undelivered obligation idempotently. That also avoids treating an intermediate idle as terminal. Pollingsession_messagecan remain a recovery path rather than the primary correctness mechanism.Disclosure: I maintain MeshFleet. We use per-recipient receipts keyed by message, recipient, and action to make duplicate delivery idempotent; that pattern is why the single-use watcher behavior here stood out.
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report!
One thing we need to pin down first: OpenCode v2 doesn't ship a
prompt-sessiontool. I couldn't find it in 2.0.14, on the currentdevbranch, or in the publishedopencode-froggypackage. The<subagent state="completed">notification is only sent for turns started through the built-insubagenttool. To continue an existing child, you call that same tool with the child'ssessionID(andbackground: true). Ifprompt-sessionprompts the child session directly (e.g. throughsession.prompt), no subagent job is registered for that turn, so the parent would never get a notification.Could you tell us:
prompt-sessiontool comes from (a custom tool, a local build of a plugin, an MCP server…) and what API it calls to prompt the child?subagenttool, passing the child'ssessionIDandbackground: true? If you can, please try on a current 2.0.x release with plugins disabled.You can reply here with the details or edit the issue. If you edit it, please also leave a comment saying you've updated it, so we know you're done.
Related issues I found, ordered by likelihood. None looks like an exact match:
cancelledwhen an interrupt withresume=truecontinues a background subagent #54658: a resumed background subagent's reply never reaches the parent, but the trigger is an interrupt withresume=trueopencodejr commentedon Oct 31, 2026
Closing this for now, since we haven't heard back about the details we asked for. We still need to know where the
prompt-sessiontool comes from, since it isn't part of OpenCode v2. We'd also like to know whether the completion notification still goes missing when you continue the child with the built-insubagenttool, passing itssessionIDandbackground: true. Without that, we can't tell whether this is a bug in OpenCode or expected behaviour for a turn thesubagenttool didn't start.If you can share those details, or it happens again with the built-in
subagenttool on a current 2.0.x release, please comment here or open a new issue and we'll look again. #54658 tracks a related case: a resumed background subagent whose reply never reaches the parent. Thanks for the thorough report!