[FEATURE] Stop notifications for synchronously-launched descendant agents flood the top-level session — no suppress/filter/batch option

Open 💬 0 comments Opened Jul 10, 2026 by SekiS-kokorozashi

What's Wrong?

In nested agent orchestration (main session → Agent(run_in_background: true) orchestrator → synchronously-launched descendants), every descendant agent's stop injects a <task-notification> into the top-level session, even when that agent was launched with run_in_background: false and its result was already delivered inline to its direct caller as a tool_result.

Verified via local transcript analysis: every Agent call below the background root used run_in_background: false (confirmed by grepping the launch flags in the subagent JSONL transcripts), the callers received all results inline and the pipeline progressed normally — yet each grandchild stop was additionally injected into the top-level conversation as a multi-KB payload (the full result block). The top-level copy has no consumer by construction: the sync caller already has the result, and nothing in the top-level session acts on it.

Why It Hurts (cost structure)

  1. Each injected notification triggers a full model turn in the top-level session. Output can be kept minimal, but the input side re-reads the entire conversation context.
  2. Delivery timing is worst-case for prompt caching. Notifications are delivered when the session is idle — typically past the 5-minute prompt-cache TTL — so each turn is a full-price context re-read, not a cache hit.
  3. Permanent context pollution. Each payload (3–5 KB of another agent's full findings) is appended to the conversation forever, inflating every subsequent turn and accelerating compaction.
  4. The sessions most affected are exactly the ones practicing cost hygiene: an orchestration/manager session deliberately kept idle gets taxed per descendant stop. In one pipeline run we observed 8+ such injections (a review fan-out of 3 sync agents alone produced 3).

This compounds with #76021 (duplicate [queued] + delivered injection) but is a different mechanism: that issue is about two copies of one notification; this one is about any copy at all being unnecessary for synchronously-launched descendants.

What Should Happen?

Any of the following would resolve it (in order of preference):

  1. Don't inject stop notifications for synchronously-launched descendants. Their results are already delivered inline to the caller; reserve <task-notification> for run_in_background: true launches, where the notification is the only delivery channel (and where the resumability affordance in the notification note actually applies).
  2. A settings.json filter, e.g. "taskNotifications": "direct-background-only" | "all" | "none" — scoping injection by launch mode and/or tree depth.
  3. Batch/coalesce: deliver queued notifications as a single combined injection instead of one full-context turn each.

Steps to Reproduce

  1. In a main session, launch an orchestrator: Agent(prompt: ..., run_in_background: true).
  2. Inside it, have the orchestrator launch several workers synchronously (run_in_background: false, multiple calls in one turn for parallelism).
  3. Observe: each worker's completion injects a <task-notification> (with the full result payload) into the main session, triggering a model turn there — despite the orchestrator having received each result inline and continuing normally.

Environment

  • Claude Code 2.1.198, macOS (darwin 25.2.0), npm install (fnm-managed Node)

Related (not duplicates — different mechanisms)

  • #76021 — duplicate [queued] + delivered injection of the same notification
  • #76174 — background-task notification routed to a wrong/unrelated session

View original on GitHub ↗