Repository navigation
Subagent permission/error events leave external integrations no way to see root-session progress (pane misreported as blocked/needs-input while subagent runs) #46685
Description
Description
Child-session events (permission.asked, question.asked, session.error) are broadcast on the global event bus with only the child's sessionID and parentID in the payload, while the parent session's own status keeps reporting busy (or goes silent) for the whole duration of a subagent run. External consumers therefore cannot distinguish "a subagent inside this root session hit a transient stream error / a rule-evaluated permission that resolves itself" from "this root session is actually waiting on the user", and there is no reliable signal on the root session telling them the subagent recovered or finished.
Concretely: the herdr integration plugin (installed via herdr integration install opencode) maps child-session permission.asked / question.asked / session.error to blocked for the pane. When a subagent starts and later hits a transient stream error (which opencode auto-retries), the pane goes red "blocked" and plays the notification sound, even though:
- the screen shows no permission/error UI at all (the subagent is streaming and editing files normally), and
- the root session status never reflected anything but
busy.
Observed on a real run: while a code-writer subagent ran for ~15 minutes, the pane stayed blocked the whole time with zero state changes, while opencode's log showed continuous subagent streaming (mode=subagent) and file edits; herdr agent explain --json reported visible_blocker: false. The same session's log later showed AI_APICallError: Cannot connect to API ... socket connection was closed unexpectedly on the subagent session, which matches the plugin's blocked trigger.
A controlled capture of the SSE event bus (GET /event) while a subagent ran shows the gap directly:
session.status ROOT {type: busy} <- last root event before the subagent starts
session.created SUB ses_... (parentID set)
session.status SUB {type: busy} xN
session.status SUB {type: idle}
session.idle SUB
session.status ROOT {type: busy} <- root only updates again after the subagent finished
No permission.* events were emitted for an auto-allowed call, and no root-session event marks "a subagent of mine started / is running / failed transiently / recovered".
Request: surface subagent lifecycle on the root session (e.g. root-visible session.status reflecting child progress, or task.* / child-session state events that integrations can use), so external integrations can avoid rendering a running subagent (or its auto-recovering stream errors) as "waiting for user input".
Plugins
herdr-agent-state.js (installed by herdr integration install opencode, v10) — its behavior is the symptom, but the underlying issue is on the opencode event model side: child-session session.error reaches plugins with no root-session context and no follow-up "recovered" event on the root session.
OpenCode version
1.18.25
Steps to reproduce
- Install the herdr integration for opencode (
herdr integration install opencode) and run opencode inside herdr (any opencode ≥1.18 with plugins). - Prompt the agent to dispatch a subagent that runs a few minutes (e.g. a code-writer subagent editing several files), or let a subagent hit a transient API stream error (it auto-retries).
- While the subagent runs normally (or during its automatic retry), the herdr pane shows red
blockedand plays the notification sound; no approval or error UI is visible in the TUI.herdr agent explain <pane> --jsonshows stateblockedwithvisible_blocker: false. - Equivalent without herdr: capture
GET /eventon the opencode server while dispatching a subagent (see event sequence above) — the root session emits nothing between subagent start and completion, while childsession.status/session.errorevents appear with only the childsessionID.
Screenshot and/or share link
Not applicable (state observed via herdr agent list / herdr agent explain; can provide on request).
Operating System
macOS 26.6 arm64
Terminal
Ghostty 1.3.1
Activity
github-actions commentedon Sep 1, 2026
This issue might be a duplicate of existing issues. Please check:
anyingiit commentedon Sep 1, 2026
Thanks for the pointers — I checked all three. None is an exact duplicate of this report, but there is real overlap with #45549 on one point.
session.error/permission.*arrive with only the child session id, the root's own status staysbusyfor the whole subagent run, and nothing on the root session marks subagent start/recovery/completion. An integration can't tell a running (or auto-retrying) subagent from a parent actually waiting on the user.session.createdcarriesinfo.parentID; childsession.statusevents carry no parent lineage at all, and the root session emits nothing between subagent start and completion. This report adds the concrete failure mode that gap causes downstream (herdr's pane plugin renders a childsession.errorduring automatic stream retry as "blocked / needs input", red state + notification sound, with no way to know it recovered).One correction to my own body text: I wrote that child events carry "only the child's sessionID and parentID" — that's true for
session.createdonly;session.statuscarries neither, which matches #30043.If maintainers prefer consolidating, the "surface subagent lifecycle on the root session" ask fits as a consumer-impact case under #45549 (point 3) / #30043 — happy to move the repro there.
Samir-Guenchi commentedon Sep 28, 2026
The current gap is that delegated child sessions emit
session.statuswith only their own session ID and status. An external consumer can therefore mistake a child becoming idle or retrying for a root-session transition unless it separately correlatessession.createdevents or fetchesSession.Info.A narrow fix would add an optional
parentIDto the existingsession.statusevent, populated centrally from the existing session record. The currentbusy/retry/idlevalues would remain unchanged; this would only make the event self-describing and would not introduce a new lifecycle system.This is a concrete case in #46685, while #30043 is the historical proposal for the same additive field. The scope here is limited to child-to-parent attribution: it does not attempt ACP permission routing, CLI NDJSON child-event forwarding, UI behavior, or root-level progress aggregation.
Would maintainers prefer this narrow
session.status.parentIDchange as the next step for #46685, or is there a preferred event/lifecycle contract?remorses commentedon Sep 30, 2026
Related V2 case from Kimaki, a Discord client (OpenCode 2.0.19): adding
rootSessionIDto child-session events would simplify routing after a reconnect. Right now I walksession.get→parentIDuntil I find a bound root, buffering child events during those lookups.An explicit subagent-link event would also help:
{ parentSessionID, childSessionID, assistantMessageID, toolCallID, agent, description, background }, including when an existing child is reused. Today I retain the parent subagent call arguments until a latertool.progress.metadata.sessionIDlinks the call to its child.Those two additions would remove ancestry lookups and the pending-call correlation map. This is a V2 API suggestion; the original report here is V1.
thdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊