No way to cancel/stop spawned agent team without killing the session

Status Closed — not planned
Maintainer reply None cached
Activity 12 comments · opened Mar 14, 2026 · closed Jul 9, 2026

Bug Report / Complaint

What happened: After Claude Code spawned multiple parallel agents without my approval (see related issue: agent proceeding without user confirmation), I was unable to stop or cancel the running agents. The only way to regain control was to kill the entire session.

Expected behavior: Users should be able to cancel or stop running agents mid-execution (e.g., via Ctrl+C, Escape, or a /stop command) without losing their session. Graceful cancellation of agent teams is essential for maintaining user control.

Impact: I was forced to kill the session entirely, losing conversation context. Combined with the unauthorized execution bug, this left me unable to prevent unwanted changes to my project. I am filing this as both a bug report and a formal complaint.

Environment:

  • Platform: macOS (Darwin 25.3.0)
  • Model: Claude Opus 4.6 (1M context)
  • Claude Code CLI

View original on GitHub ↗

11 Comments

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/34449
  2. https://github.com/anthropics/claude-code/issues/25963
  3. https://github.com/anthropics/claude-code/issues/32679

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

yurukusa · 5 months ago

Until native /stop or Ctrl+C support for agent teams lands, here are some workarounds:

1. Kill specific agent processes without losing the parent session

Agent teammates run as separate node processes. You can kill them individually:

# In another terminal, find agent processes
ps aux | grep 'claude.*--agent-id'

# Kill specific agents (not the parent)
kill <agent-pid>

The parent session survives — it'll get a "teammate exited" notification and continue.

2. Prevent unauthorized agent spawning with a PreToolUse hook

This is the root fix for the "spawned without approval" problem from #34475:

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Agent|TeamCreate",
      "hooks": [{
        "type": "command",
        "command": "bash ~/.claude/hooks/agent-gate.sh"
      }]
    }]
  }
}

~/.claude/hooks/agent-gate.sh:

#!/usr/bin/env bash
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')

# Log every agent spawn attempt
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) AGENT_SPAWN tool=$TOOL" >> /tmp/cc-agent-spawns.log

# Option A: Hard block all TeamCreate (force use of Agent tool instead)
if [[ "$TOOL" == "TeamCreate" ]]; then
  echo "BLOCKED: TeamCreate disabled. Use Agent tool with run_in_background:true instead." >&2
  exit 2
fi

# Option B: Limit concurrent agents (count running agent processes)
RUNNING=$(pgrep -f 'claude.*--agent-id' | wc -l)
if [[ "$RUNNING" -ge 3 ]]; then
  echo "BLOCKED: $RUNNING agents already running (limit: 3). Wait for one to finish." >&2
  exit 2
fi

3. Time-bomb safety net: auto-kill long-running agents

If an agent hangs or runs away, this background watchdog kills agents that exceed a time limit:

# Add to your SessionStart hook or run manually
(while true; do
  sleep 300  # check every 5 min
  for pid in $(pgrep -f 'claude.*--agent-id'); do
    ELAPSED=$(ps -o etimes= -p "$pid" 2>/dev/null | tr -d ' ')
    if [[ "$ELAPSED" -gt 1800 ]]; then  # 30 min max
      kill "$pid"
      echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) KILLED agent $pid (exceeded 30min)" >> /tmp/cc-agent-kills.log
    fi
  done
done) &

The combination of gate (prevent unauthorized spawns) + watchdog (kill runaways) + manual kill (targeted shutdown) covers most cases while waiting for a proper /stop command.

taosiyu22 · 5 months ago

same issue

taosiyu22 · 5 months ago

this issue still exist in plugin version2.1.81

ilkerbbb · 4 months ago

Adding a partial workaround for one specific failure mode in this area: stuck in-process teammates that ignore shutdown_request JSON but unblock with a plain-text message.

Context

Claude Code v2.1.89, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1, in-process backend, macOS.

We had a 5-member team where one teammate got stuck partway through its boot prompt (the boot prompt called several MCP tools that weren't available in the teammate context — see #24316 for the root cause). The stuck teammate emitted only passive idle_notification events and never produced a model turn.

We sent two consecutive SendMessage calls with {"type": "shutdown_request", ...} JSON bodies. Both succeeded at the API layer (success: true, request_id returned), but the teammate ignored both — no shutdown_response, just more idle_notifications. The other three teammates in the same team received the same shutdown protocol and returned clean shutdown_approved within seconds.

What worked

A normal SendMessage with a plain-text body broke the loop:

SendMessage <stuck-teammate>:
"Stop your current work. Reply with a shutdown_response JSON quoting request_id <id>, approve: true."

About 90 seconds later the stuck teammate emitted a clean shutdown_approved for the original request_id, and the system terminated it normally.

Hypothesis

It looks like shutdown_request JSON is delivered to the teammate's inbox but only processed in the "ready for next turn" state. A teammate stuck mid-boot or mid-turn never reaches that state, so the JSON sits unread. A plain-text message goes through the same inbox but apparently triggers the boot loop to advance, at which point the teammate sees both the new text message and the queued shutdown_request and can respond to both.

Why this is worth a fix or a doc note

shutdown_request is currently the documented protocol for terminating teammates from the lead. If it silently fails on exactly the case where you most need it (a stuck teammate), the only remaining option is killing the parent session and losing all state — which is what this issue is about.

Either:

  1. Treat shutdown_request as an interrupt that can preempt an in-progress / stuck turn rather than waiting for the teammate to finish a turn, or
  2. Add a force-terminate escape hatch (TeamMemberKill or similar) that doesn't depend on the teammate's cooperation, or
  3. At minimum, document the plain-text-wake-up trick so users have a recovery path before resorting to killing the session.

We've added the plain-text trick to our internal runbook. Posting it here in case it helps anyone else who hits this before there's a proper fix.

junaidtitan · 3 months ago

Runaway agent teams with no kill switch is scary, especially when they are burning tokens. Cozempic's guard daemon monitors agent teams and protects their state through compaction, and the doctor command can detect zombie teams that are no longer responsive. pip install cozempic https://github.com/Ruya-AI/cozempic — happy to hear how it goes.

nath-maker · 3 months ago

Same here, Summary
When TeamCreate agents stall mid-execution (commonly inside WebSearch), the lead has no working mechanism to terminate them. All three documented termination paths fail. The only recovery is for the user to close and restart Claude.

In our session, agents have now been stuck for over an hour. They continue consuming compute. The × button in the Tasks panel does nothing. This is a serious deficiency for a paid product — I am on Claude Max 20x, and the only way out is to kill the whole client.

Termination paths that fail
SendMessage with {"type": "shutdown_request"} returns success but the agent never processes its inbox while stuck in a synchronous tool call
TaskStop with the team agent ID name@team-name returns No task found with ID: ... — the team-agent ID space and TaskStop's ID space appear disjoint
TeamDelete refuses while members are active (consistent with docs, but combined with the above leaves no escape hatch)
The Tasks-panel × button signals stop but does not interrupt mid-tool-call. After an hour it still has not taken effect.
Result: stuck team members keep running until the user closes and restarts Claude. There is no programmatic or UI recovery from inside the session.

Separate issue, same session: TeamCreate registration race
One of five agents (akilah) was spawned via Agent but missing from ~/.claude/teams/{team-name}/config.json members[]. Inbox file existed (2135 bytes, accumulating routed messages), but no shutdown handshake was possible. The agent appeared idle/wake-looping from the lead's side and never delivered content.

The same race appears reproducible when 5 agents are spawned in a single tool-use block.

Reproduction
TeamCreate a team
Spawn 5 agents in one tool-use block, each prompt triggering WebSearch early
Wait until at least one agent is mid-WebSearch
Try any of the termination paths above
Compare Agent spawn count to config.json members[] length — they may differ
Impact
Live multi-agent demos break visibly with no graceful recovery
Users must close the entire Claude client to escape stuck teams
Background compute continues silently for the duration of the session
The pattern of "agent idle without delivering content" is indistinguishable to the user from "agent finished" — there is no surface signal that the team is stuck
Workaround for one related symptom
The Working with Agents documentation pattern instructs agents to append session learnings via Bash cat >> .... In sessions where Bash is not auto-approved, this stalls agents at the permission gate. We resolved this by writing prompts that explicitly forbid Bash, file writes, and self-logs (output goes only in the agent's chat message). This avoids the Bash-permission stall but does not fix the unkillable-stuck-agent or registration-race bugs above.

Environment
Claude Code, in-process backend, multi-agent team via TeamCreate
Claude Max 20x subscription
macOS, Claude Opus 4.7 (1M context)
May 7, 2026
Happy to provide session logs, team config files, and additional reproduction details.

yonatangross · 3 months ago

Real-world corroboration that the keyboard-only escape hatch makes "kill the session" the only option in some terminals:

Running Claude Code (2.1.156) inside cmux (an Electron/Ghostty multi-session manager), the watchdog repeatedly fired:

Agent "unknown" running for 217min — may be hung. Consider Ctrl+F to force-stop.

…but Ctrl+F never reaches the CC TUI — cmux/Electron captures it at the app layer (find-in-page / its own shortcut layer), so the documented force-stop is unreachable. /clear doesn't help either (background agents are preserved across /clear by design). So the hung agent could not be cleared without killing the whole session — exactly the loss-of-context this issue is about.

This is a strong argument for a non-keyboard stop affordance (a /stop or /agents stop <id> slash command, as suggested above): keyboard-only shortcuts are fragile across terminals/multiplexers that capture common chords like Ctrl+F. A secondary nice-to-have: the watchdog could surface the stoppable agent's id and a slash-command hint rather than only "press Ctrl+F".

Filed the terminal-side half against cmux: manaflow-ai/cmux#4993.

kcarriedo · 3 months ago

The lack of graceful agent cancellation is a real gap — especially in overnight or unattended orchestration scenarios where you want to abort a branch of work without nuking the entire session and losing conversation context.

The /stop command approach makes sense for single-session work. For multi-agent scenarios the need goes a level deeper: you want to cancel a specific spawned agent (and its subtree) while keeping the coordinator session alive and the remaining agents running. Right now the only option is kill the whole process group, which loses everything.

The pattern I've found that helps while this is unresolved upstream: keep the coordinator session stateless (all work state lives in files, not the conversation history), so killing and restarting costs you a few tokens rather than a whole context window. The agents that were doing work just re-read the task file on restart.

If you're building serious multi-agent workflows and hitting this regularly, I'm working on Claudeverse — an out-of-process coordinator that can signal individual agent sessions without touching the others. Happy to loop you in on the beta if that would be useful.

kcarriedo · 2 months ago

This is a real gap — the inability to cancel a running agent team without destroying the session is the kind of thing that makes autonomous workflows feel fundamentally unsafe to deploy.

The core problem as I understand it from building orchestration layers: Claude Code currently has no "stop tree" primitive. When you kill the parent process, the subagents are running in separate contexts and may continue modifying files, committing, or even spawning further agents depending on what they were mid-task on. The only safe stop today requires you to know all the PIDs (or process group) and kill them as a group.

A few things that have helped in practice until this is fixed natively:

  1. Session-level kill file: Have the orchestrator poll for the existence of a file like .claude/STOP before each tool call. Any of your own wrapper scripts or hooks can check this. Not elegant but it propagates stop intent into the agent loop without requiring a new CLI primitive.
  1. Process group isolation: If you're launching agent teams via a shell wrapper, put them in their own process group (setsid) so a single kill -9 -<pgid> stops the entire tree — parent + all subagents — atomically. This is what's missing from the current native flow.
  1. Pre-authorize a short timeout: If the concern is agents running away overnight, setting a hard --max-turns or wrapping in a timeout command at least gives you a ceiling on the damage.

None of these are substitutes for a proper /stop command that drains in-progress tool calls gracefully and preserves the conversation state. The Ctrl+C → "do you want to stop?" → "yes" flow that already exists for single sessions should extend to agent teams. Upvoting this — it's a prerequisite for trusting any unattended parallel workflow.

github-actions[bot] · 1 month ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

Showing cached comments. Read the full discussion on GitHub ↗