Repository navigation
bug(prompt): background subagent completion turn fails on claude-bridge (prompt-capture miss; appended harness likely missing) #1528
Description
Activity
Update: root cause confirmed upstream, as pointed out on elidickinson/pi-claude-bridge#144. It is earendil-works/pi#5581, still open and reproduced on pi 0.87.1: an idle
pi.sendMessage(..., { triggerTurn: true })starts the agent through_runAgentPromptwithout callingemitBeforeAgentStart. On that turn, theappendSystemPromptadditions gentle makes inbefore_agent_startnever happen. The prompt is therefore a bare prefix of the last recorded key, and claude-bridge throws rather than run the turn without the harness.In gentle this affects every idle wake-up that uses
triggerTurn, not only background subagent completions. I also reproduced it withorchestrator_send_message: the recipient coordinator session failed on both incoming messages with the same error (same 66034/68707 numbers). Both call sites are inextensions/gentle-agents.ts: the completion delivery (deliverAs: "steer") and the orchestrator message delivery (deliverAs: "followUp"). A typed user prompt always works.A workaround is possible in gentle while #5581 is pending. When the session is idle, go through
prompt()sobefore_agent_startruns. The idea was suggested in pi#5581:if (ctx.isIdle()) { pi.sendMessage(notification, { deliverAs: "nextTurn" }); pi.sendUserMessage(" "); } else { pi.sendMessage(notification, { deliverAs: "steer" }); }
This keeps the custom message metadata and gives the woken turn the full gentle harness on every provider. Without it, non-bridge providers silently run that turn without ODD, the same class of problem as #1485.
Confirming the
isIdle()workaround end to end on gentle-pi 3.7.0 + #1497, Pi 0.87.1, pi-claude-bridge 0.9.0 (Linux), with both orchestrators onclaude-bridge/claude-sonnet-5-5:Idle wake-up path Without workaround With workaround orchestrator_send_messagereceived by an idle coordinatorprompt-capture: no capture for this 51580-char system prompt … diverges at offset 15739; the recipient never repliedrecipient processed "ping 42" and replied; the sender, idle, processed the reply background subagent_runcompletion delivered to an idle parent(same failure class as reported above) completion woke the parent and the result was processed What I applied in
extensions/gentle-agents.ts: a small helper at the top of the extension that tracksagent_start/agent_endand, only when no run is active, sends the custom message withdeliverAs: "nextTurn"followed bypi.sendUserMessage(" "); otherwise it keeps the originalpi.sendMessage(message, { deliverAs, triggerTurn: true }). It is routed through all fourtriggerTurn: truecall sites (orchestrator message delivery, background completion, subagent notification, subagent query). Busy-parent behaviour is unchanged.Happy to open a PR with this once the issue is approved, if you want it before pi#5581 lands.
Correction to my comment above. The workaround as I described it has a bug. Please don't use the counter variant.
My version decided "idle" by counting
agent_start/agent_end. Pi emits that pair once per inneragent.prompt()/agent.continue()call, and one session run chains severalcontinue()calls. So the counter reads 0 between an inneragent_endand the nextcontinue()while the run is still active. Gentle flushes pending completions onagent_end, which is exactly that window.When it happens:
- The message goes to
deliverAs: "nextTurn". sendUserMessage(" ")then throws "Agent is already processing", which Pi swallows.- The message waits for the next user prompt. On my host a stale-completion notice surfaced about 10 minutes late.
The fix is what @yenhernandezmkt proposed originally: ask the host.
ctx.isIdle()reads the same run flagsendCustomMessageroutes by. The helper just has to keep the latestctx:let wakeSafeCtx: ExtensionContext | undefined; pi.on("session_start", (_e, ctx) => { wakeSafeCtx = ctx; }); pi.on("agent_start", (_e, ctx) => { wakeSafeCtx = ctx; }); pi.on("turn_end", (_e, ctx) => { wakeSafeCtx = ctx; }); pi.on("agent_end", (_e, ctx) => { wakeSafeCtx = ctx; }); const hostIdle = () => { try { return wakeSafeCtx?.isIdle() ?? false; } catch { return false; } }; const wakeSafeSend = (message, options) => { if (hostIdle()) { pi.sendMessage(message, { deliverAs: "nextTurn" }); pi.sendUserMessage(" "); return; } pi.sendMessage(message, options); };
A stale or missing
ctxfalls back to the host's ownsendMessagepath, never to thenextTurnpath.Checked: the busy-after-inner-
agent_endcase now routes tosteer(the counter version routed it tonextTurn+" "). The idle and idle-after-a-finished-run cases still go throughnextTurn+ prompt. If a PR goes ahead, it should use this version.- The message goes to
Another independent reproduction, same signature:
prompt-capture: no capture for this 55013-char system prompt, and it embeds none of the 2 known. Closest known match diverges at offset 55013 (57686-char key, last recorded at turn_start).- Again an exact prefix: the offset equals the failing prompt's length, and 2673 characters are missing from the tail, the same count as in the original report.
- Trigger: a background
gentle-ai-worker(subagent_run,mode: "background") finished while the primary session was idle. The wake-up turn only produced "No response requested." and never processed the result. - Environment: gentle-pi 3.7.0, pi-claude-bridge 0.9.0,
claude-bridgeprovider (claude-opus-5-5), Linux (CachyOS).
Until #1595 lands we are running subagents in
taskmode to avoid the idle wake-up.- addedtype:bugBug fixBug fixstatus:needs-reviewAwaiting maintainer review/approvalAwaiting maintainer review/approval
on Oct 1, 2026 - addedstatus:approvedIssue approved by maintainer; PR may be openedIssue approved by maintainer; PR may be openedand removedstatus:needs-reviewAwaiting maintainer review/approvalAwaiting maintainer review/approval
on Oct 1, 2026 - added a commit that references this issue
on Oct 3, 2026
Summary
With the
claude-bridgeprovider, the automatic turn that gentle-shell starts when a background subagent finishes can fail in pi-claude-bridge with:When that happens, the primary session's turn is lost. The model replied only "No response requested." and never processed the subagent result. The next turn, started by a user prompt, worked.
Reproduction (observed once)
claude-bridge(claude-opus-5-5, Max plan).gentle-ai-explorewithsubagent_runin background mode.gentle-agents.tsdelivers the result withpi.sendMessage(..., { deliverAs: "steer", triggerTurn: true }), which starts a new turn.Why it may involve gentle-shell
The failing prompt is an exact prefix of the last recorded prompt: the divergence offset equals the failing prompt's length, and 2673 characters are missing from the end. Since #1485, gentle's harness blocks (orchestrator, todo, and others) are added by mutating
systemPromptOptions.appendSystemPromptinbefore_agent_start. The likely explanation is that thetriggerTurnturn was rendered without those appended blocks. If so, then even without the bridge's throw, a background-completion turn would run without the gentle harness, the same silent-loss class as #1485.I have not confirmed whether this comes from pi's
triggerTurnpath or from the append mechanism. I filed the bridge side here: elidickinson/pi-claude-bridge#144Environment
Suggestion
Check that a background-subagent completion turn (
triggerTurnwhile idle) receives the same appended harness as a user-prompted turn. It may be worth a regression test with the claude-bridge provider.Related: #1485, elidickinson/pi-claude-bridge#144