Description
When a background task (spawned via run_in_background=true Agent tool) completes between turns, its TaskNotificationMessage leaks into the next receive_response() call. The model then responds to the stale task notification instead of the new user prompt.
Reproduction
import asyncio
import anyio
from claude_agent_sdk import (
ClaudeSDKClient, ClaudeAgentOptions,
AssistantMessage, TextBlock, ResultMessage,
TaskStartedMessage, TaskNotificationMessage,
)
async def main():
options = ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep", "Bash"],
permission_mode="bypassPermissions",
cwd="/tmp",
)
async with ClaudeSDKClient(options=options) as client:
# Turn 1: spawn a background task, end turn immediately
await client.query(
"Spawn a background agent (run_in_background=true) to run: sleep 60 && echo done\n"
"Then immediately say 'Spawned.' and end your turn. Do NOT wait."
)
pending = set()
async for msg in client.receive_response():
if isinstance(msg, TaskStartedMessage):
pending.add(msg.task_id)
print(f"Turn 1 done. Pending tasks: {pending}")
if not pending:
print("No pending tasks — model waited for task. Re-run to reproduce.")
return
# Wait for the background task to complete
print("Waiting 65s for background task to complete...")
await asyncio.sleep(65)
# Turn 2: simple question
await client.query("What is 2+2? Just answer with the number.")
async for msg in client.receive_response():
if isinstance(msg, TaskNotificationMessage):
print(f"*** BUG: Stale TaskNotification leaked! task_id={msg.task_id} ***")
elif isinstance(msg, AssistantMessage):
text = "".join(b.text for b in msg.content if isinstance(b, TextBlock) and b.text)
if text:
print(f"Model response: {text[:100]}")
elif isinstance(msg, ResultMessage):
result = getattr(msg, "result", "") or ""
print(f"Result: {result[:100]}")
anyio.run(main)
Note: The model may sometimes choose to wait for the background task before ending Turn 1 (no pending tasks). Re-run a few times to reproduce — it depends on model behavior.
Expected behavior
receive_response() for Turn 2 should only yield messages related to Turn 2's query. Background task notifications from Turn 1 should either:
- Be consumed by Turn 1's
receive_response() (even after ResultMessage), or
- Be filtered out / delivered through a separate channel
Actual behavior
Turn 2's receive_response() yields a stale TaskNotificationMessage from Turn 1. The model sees this notification in its conversation context, responds to it ("The background task completed successfully...") instead of answering the new query ("2+2").
Observed message flow
Turn 2 receive_response():
msg#1 TaskNotificationMessage task_id=btjnl316x status=completed ← STALE from Turn 1!
msg#2 SystemMessage subtype=init
msg#3 AssistantMessage (tool use)
msg#4 UserMessage (tool result)
msg#5 AssistantMessage "The background task completed successfully..." ← Wrong answer!
msg#6 ResultMessage
Impact
In multi-turn applications (chat bots, agents), this causes:
- Model answering questions about stale background tasks instead of the user's new message
- "Answer wrong question" behavior that confuses users
- Workarounds like
client.stop_task() are unreliable ("No task found" errors)
Environment
claude-agent-sdk 0.1.50
- Python 3.14.2
- macOS
Related issues
Description
When a background task (spawned via
run_in_background=trueAgent tool) completes between turns, itsTaskNotificationMessageleaks into the nextreceive_response()call. The model then responds to the stale task notification instead of the new user prompt.Reproduction
Note: The model may sometimes choose to wait for the background task before ending Turn 1 (no pending tasks). Re-run a few times to reproduce — it depends on model behavior.
Expected behavior
receive_response()for Turn 2 should only yield messages related to Turn 2's query. Background task notifications from Turn 1 should either:receive_response()(even afterResultMessage), orActual behavior
Turn 2's
receive_response()yields a staleTaskNotificationMessagefrom Turn 1. The model sees this notification in its conversation context, responds to it ("The background task completed successfully...") instead of answering the new query ("2+2").Observed message flow
Impact
In multi-turn applications (chat bots, agents), this causes:
client.stop_task()are unreliable ("No task found" errors)Environment
claude-agent-sdk0.1.50Related issues