Summary
If an ask/contact_supervisor(reason: "need_decision") sender disconnects before the recipient replies, the recipient can be left with a pending ask that cannot be answered or dismissed. intercom({ action: "pending" }) keeps showing the ask, but intercom({ action: "reply", ... }) fails with Session not found because the original sender session no longer exists.
This creates noisy, unresolvable supervisor-decision prompts after timed-out or interrupted subagent runs.
Environment observed
pi-intercom: 0.6.0
pi-subagents: 0.28.0
- OS: macOS / Darwin arm64
- Install:
pi install npm:pi-intercom / pi install npm:pi-subagents
Reproduction
A minimal repro does not require subagents:
-
Start two Pi sessions with pi-intercom enabled.
-
Name them so targeting is easy:
- Session A:
/name supervisor
- Session B:
/name worker
-
In session B, send a blocking ask to A:
intercom({ action: "ask", to: "supervisor", message: "Please reply after I disconnect." })
-
Before session A replies, terminate session B. For example, close the terminal, kill the process, or interrupt the child process if using a subagent.
-
In session A, inspect pending asks:
intercom({ action: "pending" })
-
Try to reply using either the default reply or the specific replyTo id shown by pending:
intercom({ action: "reply", message: "ok" })
// or
intercom({ action: "reply", replyTo: "<message-id>", message: "ok" })
-
Inspect pending asks again:
intercom({ action: "pending" })
Actual result
The reply cannot be delivered because the sender session is gone, and the pending ask remains listed.
Observed in a subagent run:
Pending asks:
- subagent-researcher-48715e9b-1 · f9c19b2c-64e9-40b5-a232-baddda294ec9 · ... · Subagent needs a supervisor decision.
Reply attempt:
Reply to "subagent-researcher-48715e9b-1" was not delivered: Session not found
Running pending again still showed the same ask.
Expected result
A pending ask whose sender is no longer connected should not remain permanently unresolved.
Any of these would be acceptable:
- automatically mark the pending ask stale when the sender disconnects
- remove/resolve the pending ask when a reply attempt fails with
Session not found
- add an explicit dismiss/clear action, e.g.
intercom({ action: "dismiss", replyTo: "..." })
- make
pending show stale/unreachable state and provide a one-command cleanup hint
Why this matters
Subagent runs often time out, get interrupted, or get superseded by a rerun. If a child sent contact_supervisor(reason: "need_decision") shortly before exiting, the supervisor session can keep receiving/seeing stale asks that are no longer actionable.
Suspected area
reply-tracker.ts stores pending asks and only removes them on explicit markReplied(...) or timeout cleanup. In the failed delivery path for reply, the tracker does not appear to mark the ask as stale or remove it.
Likely relevant paths:
reply-tracker.ts
index.ts reply action around replyTracker.resolveReplyTarget(...) / connectedClient.send(...)
- broker delivery failure reason:
Session not found
Suggested fix
When sending a reply to a pending ask returns delivered: false with reason: "Session not found", mark that pending ask stale or remove it from replyTracker. Also consider exposing explicit dismissal so users/agents can clear known-stale asks without waiting for the configured timeout.
Summary
If an
ask/contact_supervisor(reason: "need_decision")sender disconnects before the recipient replies, the recipient can be left with a pending ask that cannot be answered or dismissed.intercom({ action: "pending" })keeps showing the ask, butintercom({ action: "reply", ... })fails withSession not foundbecause the original sender session no longer exists.This creates noisy, unresolvable supervisor-decision prompts after timed-out or interrupted subagent runs.
Environment observed
pi-intercom: 0.6.0pi-subagents: 0.28.0pi install npm:pi-intercom/pi install npm:pi-subagentsReproduction
A minimal repro does not require subagents:
Start two Pi sessions with
pi-intercomenabled.Name them so targeting is easy:
/name supervisor/name workerIn session B, send a blocking ask to A:
Before session A replies, terminate session B. For example, close the terminal, kill the process, or interrupt the child process if using a subagent.
In session A, inspect pending asks:
Try to reply using either the default reply or the specific
replyToid shown by pending:Inspect pending asks again:
Actual result
The reply cannot be delivered because the sender session is gone, and the pending ask remains listed.
Observed in a subagent run:
Reply attempt:
Running
pendingagain still showed the same ask.Expected result
A pending ask whose sender is no longer connected should not remain permanently unresolved.
Any of these would be acceptable:
Session not foundintercom({ action: "dismiss", replyTo: "..." })pendingshow stale/unreachable state and provide a one-command cleanup hintWhy this matters
Subagent runs often time out, get interrupted, or get superseded by a rerun. If a child sent
contact_supervisor(reason: "need_decision")shortly before exiting, the supervisor session can keep receiving/seeing stale asks that are no longer actionable.Suspected area
reply-tracker.tsstores pending asks and only removes them on explicitmarkReplied(...)or timeout cleanup. In the failed delivery path forreply, the tracker does not appear to mark the ask as stale or remove it.Likely relevant paths:
reply-tracker.tsindex.tsreply action aroundreplyTracker.resolveReplyTarget(...)/connectedClient.send(...)Session not foundSuggested fix
When sending a reply to a pending ask returns
delivered: falsewithreason: "Session not found", mark that pending ask stale or remove it fromreplyTracker. Also consider exposing explicit dismissal so users/agents can clear known-stale asks without waiting for the configured timeout.