[BUG] Bash run_in_background=true silently drops completion notifications for 2nd+ concurrent tasks
What's Wrong
I run multiple concurrent Bash background tasks from a long-lived streaming session (daemon mode). Only the first task's task_notification is ever delivered. The rest complete successfully — I can verify their output files — but their notifications never arrive. No errors, no warnings. Silent drop.
Agent run_in_background=true works perfectly in the exact same scenario. Every notification lands.
I discovered this after debugging why my background monitoring pipeline was silently losing results. The asymmetry between Bash and Agent was the giveaway.
Steps to Reproduce
Launch 3 concurrent Bash background tasks from a streaming session:
import { query } from "@anthropic-ai/claude-agent-sdk";
async function* streamingGen() {
yield {
type: "user" as const,
message: {
role: "user" as const,
content:
"Launch three Bash background tasks simultaneously:\n" +
'1. `sleep 10 && echo "task_a done"` with run_in_background=true\n' +
'2. `sleep 20 && echo "task_b done"` with run_in_background=true\n' +
'3. `sleep 30 && echo "task_c done"` with run_in_background=true\n\n' +
"After launching all three, say: THREE_LAUNCHED. Do NOT check on the tasks."
}
};
// Simulate daemon: stay alive long enough for all tasks to complete
await new Promise((r) => setTimeout(r, 60000));
}
const q = query({
prompt: streamingGen(),
options: { maxTurns: 30, permissionMode: "bypassPermissions", includePartialMessages: true }
});
let notifCount = 0;
for await (const msg of q) {
const m = msg as Record<string, unknown>;
if (m.type === "system" && m.subtype === "task_notification") {
notifCount++;
console.log(`task_notification #${notifCount}: ${m.task_id} status=${m.status}`);
}
}
console.log(`Total: ${notifCount} / 3 expected`);
// Expected: 3
// Actual: 1
Expected: 3 task_notification messages
Actual: 1 task_notification message (tasks 2 and 3 silently dropped)
The bug does not reproduce when using Agent tool instead of Bash — Agent notifications are always delivered reliably.
Root Cause
The CLI's result stashing condition only matches local_agent and local_workflow task types:
// cli.js (simplified)
if (
getTaskEntries(appState).some(
(t) => (t.type === "local_agent" || t.type === "local_workflow") && isPending(t)
)
) {
P = message; // stash result → polling loop stays alive
} else {
outputStream.enqueue(message); // deliver immediately → i() exits
}
When a Bash background task is pending:
- Task type is
local_bash→ not matched → result delivered immediately →i()exits - Later task completions enqueue their
task-notificationwith priority"later" - But
i()is already gone — nobody re-enters to dequeue them - Notifications are stuck in the queue forever
When an Agent background task is pending:
- Task type is
local_agent→ matched → result stashed → polling loop stays alive - All subsequent notifications are consumed correctly
The first Bash notification sometimes arrives because the task may complete during i()'s execution window — it gets picked up before i() exits. Tasks 2 and 3 complete after i() has already exited.
Suggested Fix
Add local_bash to the result stashing condition:
// Before:
(t.type === "local_agent" || t.type === "local_workflow") && isPending(t)
// After:
(t.type === "local_agent" || t.type === "local_workflow" || t.type === "local_bash") && isPending(t)
Alternatively, make the task-notification enqueue for Bash tasks trigger i() re-entry directly (the way "now" priority commands do), so notifications are consumed regardless of polling loop state.
Environment
- Claude Code Version: 2.1.81
- SDK:
@anthropic-ai/claude-agent-sdk0.2.81 - Platform: Anthropic API (first-party)
- OS: macOS (Apple Silicon)
- Shell: zsh
Workaround
Use Agent tool with run_in_background=true for all background tasks that require reliable completion callbacks. Agent wraps the Bash command and uses local_agent task type, which correctly triggers the polling loop.
This works, but it's a meaningful overhead difference — wrapping every background shell command in an Agent just to get reliable notifications is not ideal.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗