Repository navigation
fix(opencode): a tool launch's session-URL notice never wakes an idle root - #1734
Merged
Merged
Conversation
… root On OpenCode 2 a plannotator tool call from a background subagent (or an API-created child session) posted its session-ready notice into the ROOT session, which was idle. The pending steer row was then promoted alone as a model turn when the root next woke, and the model answered the URL line. Tool launches now post the notice into the calling session, and only while the tool call is still open, so it is always promoted inside the caller's running turn and dropped from the model context as before. The decision still goes to the root, queued. Slash commands are unchanged (#1515).
1 task
backnotprop
added a commit
that referenced
this pull request
Oct 17, 2026
…d while an Ask holds it - The fallback queued turn ends with a repeat line, and the plugin's context and generate hooks drop the later of the steer row and its fallback from every model request (OpenCode promotes the steer row with the queued turn, #1734, and cannot withdraw it). - session.inbox.delivery.changed to queue for the steer row counts as delivered: no fallback. - The steer waits at most 60 s, and the fallback goes at once when the event stream ends. - The mod's send_now (poll and state events) is true only while a reply could go in now: while this session's own Ask question holds it, Send now is not offered and a pressed one reads Queued. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
backnotprop
added a commit
that referenced
this pull request
Oct 17, 2026
…running turn (#1968) * Inbox: say a sent reply is queued, and Send now delivers it into the running turn The line under the person's reply said "Saved for Claude Code. It sees it when it checks." while the reply waited for the session to go idle, which read as dropped. It now says where the reply is, from the session's connection: "Queued · Claude Code is working; it gets this when its turn ends." with Send now, "Sending to Claude Code…" while idle, and the truthful "Saved for … It sees it when it next reads this thread." for an agent that only reads the thread. Send now (POST /api/inbox/messages/:id/send-now, door 7.58) marks the reply deliver_now_at; the bridge command carries now: true. The Claude Code mod puts it into the running turn as a user row with $.session.append (a plugin's $.prompt.submit never folds into a running turn), with one short follow-up turn if the turn ended before any request carried it; Pi steers (deliverAs: "steer"); OpenCode 2 prompts with delivery: "steer". Connections advertise it with the poll field send_now. Also fixes the mod counting a subagent's turn.start as the session's turn: its end was never counted, so a background subagent left the session busy for good and Inbox replies never went in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Inbox: keep the plannotator_inbox description under Claude Code's 2,048 characters The Send now sentence in the tool's wake note pushed it to 2,122; the wake text and plannotator inbox --help already say it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Inbox: the sessions route's asker answer now carries connection Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Inbox: the phone list test reads the Sent row's new wording Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Inbox Send now: decide at the append, never into an Ask turn, confirm steers Review fixes: - The mod decides into-the-turn right before the append, after the fresh check, lease and claim: a turn that ended meanwhile gets the reply as a turn; a turn that ended or changed during the append gets the pointer turn at once; rows are tied to their turn id (several per turn). - Never into this session's own Ask this session turn, running or queued (steerableTurn); the reply waits for idle there. - Send now is advertised only on an engine whose API has $.session.append (2.1.301+, from $.session.version(): validate refuses testing the noun). - OpenCode 2 counts a steer delivered only on session.inbox.delivered for its row; if the run ends first, it is sent queued after a grace. Pi tested on message_start. - The server hands `now` only to a poll that said send_now; a refused steer posts send_now_failed and the line reads queued again; urgent delivery is oldest first; the line reads Queued for a shim-sent message whose session polls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Inbox Send now: OpenCode 2 never shows the model a reply twice; Queued while an Ask holds it - The fallback queued turn ends with a repeat line, and the plugin's context and generate hooks drop the later of the steer row and its fallback from every model request (OpenCode promotes the steer row with the queued turn, #1734, and cannot withdraw it). - session.inbox.delivery.changed to queue for the steer row counts as delivered: no fallback. - The steer waits at most 60 s, and the fallback goes at once when the event stream ends. - The mod's send_now (poll and state events) is true only while a reply could go in now: while this session's own Ask question holds it, Send now is not offered and a pressed one reads Queued. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Inbox Send now: OpenCode 2 never gives up a steer mid-run, and a repeat never empties the last message - OpenCode 2: no cap while the run goes on (a long tool call no longer sends the fallback while OpenCode will still promote the steer row); a lost end event is checked with session.wait, and after the event stream ends the row waits for idle, then counts as delivered when session.context holds it. - dropInboxDuplicates replaces a later copy that is its message's whole content with a stable pointer instead of removing it, so a request never ends on an assistant message. - Pi and OpenCode never steer into the session's own Ask question (canSteer), and InboxWake's state events carry send_now, as the mod's do. - The mod keeps alive fresh for every claim it holds while it waits. - Pi: a steer Esc moved into the editor is taken back out before the reply goes in as a follow-up. - The 👍 line uses the reply line's honest Queued / Sending words from the session's connection (no Send now for an ack). * Inbox e2e: the 👍 line reads the session's connection (when its session is back, then is working, no Send now) * Inbox: a claim the mod cannot use at once is given back, so the person's process delivers it; OpenCode 2 sends no steer fallback into a deleted session or during shutdown The Claude Code mod kept a claim it won but could not use (a turn started right after the claim, its own Ask question, a refused row) and refreshed its alive while not the lease holder, so with the lease moved to another process nobody delivered the reply. It now gives the claim back (INBOX_RELEASE_SCRIPT, only while it is its own and neither delivered nor reported), and a process that lost the claim takes the reply back as pending once the folder is gone. OpenCode 2: session.deleted settles that session's steer rows (their watch stops) and dispose settles them as abandoned: no fallback is sent; the wake gives its claim back and the reply is picked up at the next start. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Found in the 0.28.5 smoke, OpenCode 2 with the
plannotatortool turned on.Bug
A background subagent (
subagentwithbackground: true) opens a review with the tool. Its "Plannotator session ready: " notice was posted into the ROOT session, which is idle at that point. The pending steer row then got promoted on its own as a model turn the next time the root woke up. The model answered the URL line, and that exchange stayed in every later request. The same thing happens for a child session created through the API (parentID). Decisions were always delivered correctly. Foreground subagents were not affected because the root is mid-turn while it waits on them.Cause
runPlannotatorToolhands the launch the root session (which is correct for the decision), andrunNativeCommandposted the notice into that same session. WithalwaysQueue, the queued decision promotes one row at a time, so the steer notice ahead of it ran as its own turn. Because it was then the last message after an assistant reply,dropSessionUrlNoticeskept it on purpose.Fix
runPlannotatorToolcloses that window the moment it answers. The caller is therefore always mid-turn when the notice arrives, so the notice is promoted inside that turn and dropped from the model's context, exactly like a main-session tool call today.listand in the log.createV2BridgeClientgainsnotice: { sessionID, open }. Co-promotion is now only attempted for a notice in the decision's own session.Tests
plannotator-tool.test.ts, through the real launch path with a stub CLI:queuenative-commands.test.ts: a notice in another session never steers the decision, and a closed window posts nothing.bun testinapps/opencode-plugin: 342 pass.bun run typecheckpasses. The touched opencode files also typecheck under strict tsc.build:review+build:hook+build:opencodesucceed.Live check (OpenCode 2.0.22, fake LLM, sandboxed HOME/XDG/data dir, local registry)
ECHO: Plannotator session ready: …)In my harness the background-subagent run did not reproduce the leak on the 0.28.5 build. The root had already finished its turn before the notice arrived, so the notice was co-promoted with the subagent's completion. The smoke's leak needs that race. The child-session case reproduces it every time, and it is the same code path.