Sub-agent sync-vs-async is session-host-dependent (not version/flag); run_in_background:false not reliably honored — no documented way to force synchronous execution

Status Open
Reported on v2.1.183
Maintainer reply None cached
Activity 6 comments · opened Jun 20, 2026

Description

Whether an Agent() / Task sub-agent runs synchronously (final message returned inline as the tool result, in the same turn) vs asynchronously (background task + task-notification on completion) is not controllable via a documented API, and it differs between sessions at the same Claude Code version and the same flags. run_in_background: false is honored in some sessions and silently ignored in others.

Version / environment (both sessions identical)

  • Claude Code 2.1.183
  • CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1

Repro / observation

Call the Agent/Task tool with run_in_background: false explicitly set (e.g. prompt: reply PONG):

  • Session A — env included CLAUDE_CODE_CHILD_SESSION=1 (a child session): the call returns PONG inline as the tool result, synchronously, in the same turn.
  • Session B — top-level interactive session, same version, same AGENT_TEAMS flag: the same call launches asyncAsync agent launched … working in the background … notified when it completes — and the result arrives later as a task-notification. run_in_background: false is effectively ignored (always async, 3/3 attempts).

So sync-vs-async is determined by some session / hosting factor (appears related to CLAUDE_CODE_CHILD_SESSION), not by the version or the AGENT_TEAMS flag.

Expected vs actual

  • Expected: identical version + flags produce identical, controllable behavior; run_in_background: false is honored consistently (synchronous, inline return).
  • Actual: behavior depends on an undocumented session/host factor; run_in_background: false is honored in some sessions and ignored in others, with no documented way to tell which you will get.

Why this matters

  • run_in_background is not documented as an Agent/Task tool parameter, and there is no documented, reliable way to force synchronous (foreground, inline-returning) sub-agent execution.
  • The async path's completion signal is the task-notification, which is already known to be dropped / duplicated / mis-routed (#68997, #60001, #68065, #68749). An orchestrator that depends on a sub-agent's result therefore cannot escape that unreliable channel by forcing synchronous execution — because synchronous execution is not reliably available. This blocks a clean fix for the notification-drop stall in any multi-agent / agent-team workflow: the orchestrator is forced onto a channel that can silently drop, with only out-of-band polling as a workaround.

Requests (clarification)

  1. Document the Agent/Task tool parameter schema, including run_in_background, and when/whether it is honored.
  2. Either provide a reliable, documented way to force synchronous foreground sub-agent execution, or document exactly what determines sync vs async (session type / CLAUDE_CODE_CHILD_SESSION / launch mode), so it becomes controllable.

Related

  • #68997 — background-agent completion notifications silently dropped (sequential dispatch; agents complete cleanly, no task-notification fires).
  • #60001 — silent drops with ≥3 parallel dispatches.
  • #68065 — completion notifications routed to the wrong agent ID (sequential dispatch).
  • #68749 — sub-agent not woken by the completion of its last pending background task.

Those four are the downstream symptom (the async channel is unreliable). This issue is the upstream question they all run into: there is no reliable way to avoid the async channel by running the sub-agent synchronously.

View original on GitHub ↗

6 Comments

kcarriedo · 2 months ago

The CLAUDE_CODE_CHILD_SESSION dependency you've identified is the key. It means sync-vs-async is a function of how the session was launched rather than anything the calling code can control at dispatch time -- which is a bad API contract for anything trying to build reliable orchestration on top of it.

The practical consequence for anyone writing a coordinator: you can't write "give me the result of this sub-task synchronously" -- you're forced onto the async notification path, and as the related issues show, that path has multiple known drop/misroute failure modes. The absence of a reliable sync path isn't just a documentation gap -- it closes off a whole class of workaround for the notification reliability problems.

Worth flagging: the set of related issues you've linked (#68997, #60001, #68065, #68749) all share the same observable symptom -- the orchestrator stalls waiting for a result that never arrives. But the root causes differ (dropped envelope, misrouted delivery, wrong agent ID). The sync-vs-async controllability gap means none of those can be escaped by the calling code; you're always at the mercy of the delivery channel.

A minimal fix path: document that CLAUDE_CODE_CHILD_SESSION=1 produces synchronous dispatch and make that settable at the session level (not just via env at process spawn), so a top-level interactive session can opt into sync dispatch for a specific Agent call.

kcarriedo · 2 months ago

Ran into the same behavior when building a polling runner that dispatches agent subprocesses. The sync-vs-async inconsistency appears to correlate with whether the initiating session has CLAUDE_CODE_CHILD_SESSION=1 set -- child sessions honor run_in_background: false because they don't have a live TUI driving the background dispatch loop, so the agent tool falls back to inline execution.

A workaround that worked for us: before spawning subagents that need inline results, explicitly set CLAUDE_CODE_CHILD_SESSION=1 in the subprocess environment. Not documented anywhere, but it flips the dispatch path.

The deeper problem you're pointing at -- that there's no documented, reliable API contract for sync vs async agent execution -- is real. The task-notification channel dropping/duplicating messages makes it impossible to build deterministic orchestration on top of the current agent primitives. Would be very valuable if Anthropic documented exactly what determines the dispatch path so callers can predict and control it.

kcarriedo · 2 months ago

This hits a real pattern. The sync-vs-async inconsistency across session types is exactly the kind of undocumented behavior that breaks orchestration workflows at scale.

One thing worth noting from running multi-agent setups in production: the session host factor you're describing maps pretty closely to whether the session was spawned with an inherited environment or a clean one. Child sessions (CLAUDE_CODE_CHILD_SESSION=1) seem to get a different agent-loop configuration than top-level interactive sessions, which is why run_in_background: false gets honored in one context and silently dropped in the other. The trouble is there's no documented invariant for which you'll get, so you can't write portable orchestration code against it.

The dropped/duplicated task-notification channel compounds this badly. If sync execution were reliable, you could avoid the async notification path entirely for latency-sensitive orchestration steps. Right now you're forced into async even when you don't want it, then dealing with a notification channel that has known reliability issues on top.

What I've seen work as a temporary workaround: structuring orchestration so the orchestrator polls a shared state file rather than waiting on notifications. It's ugly and adds latency, but it's deterministic. Not a fix for the underlying issue - just a way to keep workflows moving while this gets sorted.

Interested to see what Anthropic documents here. The run_in_background parameter existing but being inconsistently honored is worse than it not existing at all.

kcarriedo · 2 months ago

The session-host-dependent sync/async behavior you're describing here is something we've also hit building an orchestration layer on top of Agent Teams. The run_in_background parameter behaving differently based on whether you're in a child session vs. a top-level interactive session is not documented anywhere, which makes it impossible to design around reliably.

One thing we found: if you structure your orchestrator so that each subagent writes a structured handoff artifact (a JSON file with a typed schema and a required "status" field) as its last action, you can poll for that file's existence as an out-of-band completion signal instead of relying on the notification channel. It is not elegant but it sidesteps the async notification drop entirely.

The broader issue is that the Agent/Task tool parameter schema is not documented at all -- run_in_background, timeout, and the session-host condition all need to be in the docs if anyone is going to build reliable orchestrators on top of this. Your two requests (document the schema, provide a reliable way to force synchronous execution) are exactly the right asks.

Following this one closely.

kcarriedo · 2 months ago

The session-host-dependent behavior you documented here is the kind of thing that only becomes visible once you are running orchestrators at scale -- a one-off sub-agent call works fine, but the moment you are coordinating 3+ agents the notification-drop and sync/async inconsistency starts producing intermittent stalls that are very hard to reproduce in isolation.

The three failure modes you identified (silent drops on sequential dispatch, drops at 3+ parallel agents, mis-routed notifications) add up to orchestrators having no reliable signal about sub-agent completion state. The workaround of polling for output files or using shared disk state is workable but forces orchestrator logic into territory it should not have to touch.

On the sync-vs-async inconsistency specifically: the CLAUDE_CODE_CHILD_SESSION=1 behavior difference suggests the async path is a top-level-session assumption baked into the runtime rather than a deliberate API design choice. That makes documenting it hard because the behavior is tied to an internal session-type flag rather than a stable API surface.

The ask -- document the parameter schema OR provide a reliable way to force synchronous execution -- is the minimum needed to write orchestrators that are not guessing about execution semantics. The notification reliability issue is a separate but related blocker. Both need to land before multi-agent orchestration is production-grade.

I have been running into variants of this pattern tracking agent pipeline reliability across multi-session setups. The notification-drop stall is one of the top three sources of phantom "stuck" agents that are actually just waiting on a dropped signal. Good write-up of the root cause here.

Thf772 · 1 month ago

A related issue with this is that the environment variable CLAUDE_CODE_DISABLE_BACKGROUND_TASKS is not honored when running agents (observed on Claude Code CLI 2.1.202). According to the documentation, this variable when set should force all tasks including sub-agents to block the main agent until finished, but that is not the case (at least in interactive sessions).

As a side note, I run Claude Code locally with an Ollama backend. Because this backend only processes one AI request at a time, whenever Claude launches sub-agents I get at least 3 "Task is not done - waiting for result" from the main agent, which wastes a lot of time.