Summary
sendAndWait awaits session.idle. The CLI fires session.idle when the orchestrator's turn ends — including when the orchestrator ends its turn after dispatching task calls. The SDK does not track in-flight sub-agents; task is fire-and-forget from the SDK's perspective. Consumers' code believes the session is complete and tears down state while sub-agents are still running.
Repro
const session = await client.createSession({
customAgents: [
{ name: "scanner", description: "Scans codebase", prompt: "..." },
{ name: "triager", description: "Triages findings", prompt: "..." },
],
});
await session.sendAndWait({
prompt: "Dispatch a scanner agent to analyze the codebase, then a triager to rank findings.",
});
// `sendAndWait` resolves shortly after the orchestrator's `task(scanner)` + `task(triager)` calls
// — BEFORE the sub-agents have completed their work.
Expected
sendAndWait resolves only when all dispatched sub-agents have completed.
Actual
sendAndWait resolves on the first session.idle event. If the orchestrator's turn ends after dispatching but before sub-agent completion, the session is considered idle and sendAndWait returns. Sub-agents may still be running.
Frequency observed in another harness: ~30% of multi-sub-agent dispatch sessions.
Evidence (SDK source)
nodejs/src/session.ts: sendAndWait() listens for event.type === "session.idle" and calls resolveIdle() on the first occurrence. No task-dispatch tracking, no sub-agent counter, no subagent.completed accumulation.
Workaround (consumer-side)
Either:
- Poll for a terminal artifact path on the filesystem (works if the sub-agents are expected to write a known output file).
- Maintain a sub-agent counter in user code, incremented on
subagent.started and decremented on subagent.completed. Only treat the session as complete when session.idle AND counter == 0.
Both reinvent state machine logic the SDK is best positioned to provide.
Suggested fix
- Make
sendAndWait track dispatched sub-agents (via subagent.started / subagent.completed) and resolve only when the session is idle AND all sub-agents have completed.
- Or add a new
sendAndWaitForCompletion({ prompt }) API consumers can opt into.
Related
Environment
- SDK: @github/copilot-sdk@0.3.0
- CLI: @github/copilot@1.0.45
- Node: 22 LTS
- OS: Windows 11
- Model: claude-sonnet-4-6
Summary
sendAndWaitawaitssession.idle. The CLI firessession.idlewhen the orchestrator's turn ends — including when the orchestrator ends its turn after dispatchingtaskcalls. The SDK does not track in-flight sub-agents;taskis fire-and-forget from the SDK's perspective. Consumers' code believes the session is complete and tears down state while sub-agents are still running.Repro
Expected
sendAndWaitresolves only when all dispatched sub-agents have completed.Actual
sendAndWaitresolves on the firstsession.idleevent. If the orchestrator's turn ends after dispatching but before sub-agent completion, the session is considered idle andsendAndWaitreturns. Sub-agents may still be running.Frequency observed in another harness: ~30% of multi-sub-agent dispatch sessions.
Evidence (SDK source)
nodejs/src/session.ts:sendAndWait()listens forevent.type === "session.idle"and callsresolveIdle()on the first occurrence. Notask-dispatch tracking, no sub-agent counter, nosubagent.completedaccumulation.Workaround (consumer-side)
Either:
subagent.startedand decremented onsubagent.completed. Only treat the session as complete whensession.idleAND counter == 0.Both reinvent state machine logic the SDK is best positioned to provide.
Suggested fix
sendAndWaittrack dispatched sub-agents (viasubagent.started/subagent.completed) and resolve only when the session is idle AND all sub-agents have completed.sendAndWaitForCompletion({ prompt })API consumers can opt into.Related
SessionHooks(onPreToolUse/onPostToolUse) are not invoked for tool calls made by sub-agents spawned viatask#1097 —SessionHooks(onPreToolUse/onPostToolUse) not invoked for tool calls made by sub-agents spawned viatask. Same theme of "sub-agent lifecycle isn't fully surfaced through the SDK's consumer-facing surface."session.disconnect()is cooperative; doesn't abortsendAndWaitor close the transport #1273 —session.disconnect()cooperative. Compounds with this issue: when the consumer notices premature resolution and tries to recover, the disconnect path is also broken.Environment