Skip to content

bug(prompt): background subagent completion turn fails on claude-bridge (prompt-capture miss; appended harness likely missing) #1528

Description

@yenhernandezmkt

Summary

With the claude-bridge provider, the automatic turn that gentle-shell starts when a background subagent finishes can fail in pi-claude-bridge with:

Error: prompt-capture: no capture for this 66034-char system prompt, and it embeds none of the 2 known.
Closest known match diverges at offset 66034 (68707-char key, last recorded at turn_start).

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)

  1. Primary session on claude-bridge (claude-opus-5-5, Max plan).
  2. Launch gentle-ai-explore with subagent_run in background mode.
  3. The primary turn ends and the session goes idle.
  4. The subagent finishes. gentle-agents.ts delivers the result with pi.sendMessage(..., { deliverAs: "steer", triggerTurn: true }), which starts a new turn.
  5. The bridge throws the error above.

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.appendSystemPrompt in before_agent_start. The likely explanation is that the triggerTurn turn 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 triggerTurn path or from the append mechanism. I filed the bridge side here: elidickinson/pi-claude-bridge#144

Environment

  • gentle-shell / gentle-pi 3.7.0 (git package, HEAD f14b19a)
  • pi 0.87.1, pi-claude-bridge 0.9.0, gentle-engram 0.1.16, pi-mcp-adapter, pi-web-access
  • Background subagent policy: on; Linux

Suggestion

Check that a background-subagent completion turn (triggerTurn while 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

Activity

  1. yenhernandezmkt commented on Sep 28, 2026

    @yenhernandezmkt
    Author

    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 _runAgentPrompt without calling emitBeforeAgentStart. On that turn, the appendSystemPrompt additions gentle makes in before_agent_start never 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 with orchestrator_send_message: the recipient coordinator session failed on both incoming messages with the same error (same 66034/68707 numbers). Both call sites are in extensions/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() so before_agent_start runs. 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.

  2. BGamboa13 commented on Sep 29, 2026

    @BGamboa13

    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 on claude-bridge/claude-sonnet-5-5:

    Idle wake-up path Without workaround With workaround
    orchestrator_send_message received by an idle coordinator prompt-capture: no capture for this 51580-char system prompt … diverges at offset 15739; the recipient never replied recipient processed "ping 42" and replied; the sender, idle, processed the reply
    background subagent_run completion 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 tracks agent_start/agent_end and, only when no run is active, sends the custom message with deliverAs: "nextTurn" followed by pi.sendUserMessage(" "); otherwise it keeps the original pi.sendMessage(message, { deliverAs, triggerTurn: true }). It is routed through all four triggerTurn: true call 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.

  3. BGamboa13 commented on Sep 30, 2026

    @BGamboa13

    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 inner agent.prompt() / agent.continue() call, and one session run chains several continue() calls. So the counter reads 0 between an inner agent_end and the next continue() while the run is still active. Gentle flushes pending completions on agent_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 flag sendCustomMessage routes by. The helper just has to keep the latest ctx:

    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 ctx falls back to the host's own sendMessage path, never to the nextTurn path.

    Checked: the busy-after-inner-agent_end case now routes to steer (the counter version routed it to nextTurn + " "). The idle and idle-after-a-finished-run cases still go through nextTurn + prompt. If a PR goes ahead, it should use this version.

  4. hectormr206 commented on Sep 30, 2026

    @hectormr206

    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-bridge provider (claude-opus-5-5), Linux (CachyOS).

    Until #1595 lands we are running subagents in task mode to avoid the idle wake-up.

  5. added
    status:approvedIssue approved by maintainer; PR may be opened
    and removed on Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingstatus:approvedIssue approved by maintainer; PR may be openedtype:bugBug fix

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions