[BUG] Background Agent tasks persist as "running" in /workflows after full process restart on both dispatch and viewing hosts

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

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Background Agent subagent task records survive a full Claude Code process restart on both the dispatch host AND independent viewing hosts, continuing to display as "running" in /workflows indefinitely.

After dispatching multiple background Agent subagents (run_in_background: true) in a session, the subagents complete normally — tool-result notifications fire, results visible inline, the orchestrator continues working with the returned data. However, the /workflows UI continues to show those tasks as actively running, with elapsed-time counters incrementing past completion. This persists through:

  • Full Claude Code CLI restart on the dispatch host
  • Independent restart of Claude.ai web UI and Claude Code on a separate viewing host
  • Multiple dispatch/restart cycles over a multi-day project (2026-06-04 through 2026-06-06)

TaskStop invoked on the affected IDs returns "No task found" (the orchestrator-side namespace doesn't match the /workflows UI's view) yet the UI keeps showing them active. IDs derived from the .output filenames in ~/AppData/Local/Temp/claude/.../tasks/ are likewise unreachable by TaskStop. State surviving a clean restart of both clients on different machines strongly suggests server-side state retention without a corresponding reap on client reconnect.

What Should Happen?

Either of these would resolve it:

  1. Reap on client reconnect. When a Claude Code client establishes a session with completed background-task records older than some sane threshold (e.g. last result-notification + 1 hour), the server-side state machine transitions them to a terminal state and the UI reflects that.
  1. Transition on result-notification. When a background Agent subagent's tool-result notification fires (the same event the orchestrator already consumes inline), the server-side task-lifecycle state machine should transition the task to completed. Today the notification appears to reach the orchestrator but not flow through to the /workflows UI's data source.

Either way: /workflows should be a real-time reflection of in-flight work, not a monotonically-growing ledger of historical dispatches.

Error Messages/Logs

Error Messages/Logs: (empty — no error output; this is a silent state-leak, not a crash)

Steps to Reproduce

  1. Open a Claude Code CLI session on Host A.
  2. From the model, dispatch ≥3 background Agent subagents in a single message:

Agent({ description: "smoke test 1", subagent_type: "general-purpose",
prompt: "say hello and exit", run_in_background: true })
Agent({ description: "smoke test 2", subagent_type: "general-purpose",
prompt: "say hello and exit", run_in_background: true })
Agent({ description: "smoke test 3", subagent_type: "general-purpose",
prompt: "say hello and exit", run_in_background: true })

  1. Wait for each <task-notification> with <status>completed</status> to arrive in the conversation (typically 30-90s per subagent).
  2. Open /workflows from Host A — observe the three tasks listed as actively running.
  3. Quit the Claude Code CLI on Host A entirely (close the terminal window or Ctrl-D out, ensure process is dead).
  4. Independently restart any Claude.ai web UI or Claude Code instance on Host B.
  5. Open /workflows from either host.

Result: the three smoke-test tasks are still listed as actively running, with elapsed-time counters incrementing past their actual completion times. Try invoking TaskStop on any of the IDs — it returns "No task found", but the UI still shows them active.
Continue dispatching subagents over a multi-day window and the zombie set accumulates monotonically. After ~48 hours of normal use we observed 20+ zombie entries in /workflows, blocking practical use of the UI as a status surface.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.165 (Claude Code)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

Local workaround built around the issue

We hit this in the context of building a local agent-orchestration framework. Since the /workflows UI namespace was opaque to our TaskStop calls, we built a parallel append-only audit graph on our own bus to track the truth:

  • agent.dispatched event logged before every Agent tool call (carries task_id, prompt summary, expected scope, parent run id).
  • agent.completed event logged when the tool-result notification fires (carries exit status, result summary, usage).
  • agent.stopped event logged on every TaskStop attempt — including namespace_hit: false cases, which are the orchestrator-side mismatch this bug exposes.
  • A periodic reaper queries dispatched without matching completed/stopped past a stale threshold and emits agent.zombie events for our dashboard.

The pattern works around the harness opacity but doesn't address the underlying state persistence. Our local bus correctly identifies zombies; the /workflows UI is still showing them as live because its data source apparently doesn't know they're dead.

Why this matters for Claude Code itself

If the server-side task-lifecycle state machine had a last_notification_at timestamp and a passive sweep, it could self-diagnose stuck dispatches the same way our reaper does — dispatched without completed for N hours = zombie, mark terminal. Same pattern, just one altitude up.

Environment notes

  • Both hosts (X dispatch, Yviewing) on the same LAN
  • Different Anthropic API plans — issue reproduces on Max plan
  • No proxy/firewall between hosts and Anthropic API
  • Background subagents were general-purpose type for the affected cases

View original on GitHub ↗

6 Comments

kcarriedo · 2 months ago

The audit-graph workaround you've built — agent.dispatched / agent.completed / agent.zombie events on your own bus — is the right pattern for working around this today, and the fact that you needed to build it surfaces the core gap: there's no first-class way to query "which of my dispatched agents is actually still running" without trusting the /workflows UI, which apparently doesn't reflect the true completion state.

The namespace mismatch (TaskStop returns "No task found") is particularly telling. It suggests the task ID the orchestrator sees and the ID /workflows uses for the same agent may not be the same reference — which would explain why the state can't be cleaned up even when the completion notification already fired on the orchestrator side.

The server-side reap-on-reconnect approach you've proposed (Option 1) is the right long-term fix because it's the only one that doesn't require the client to have seen the completion event. If the server-side state machine can mark a task terminal based on last_notification_at + threshold, that covers the case where the dispatch host died before the result notification was consumed.

For anyone building similar external orchestrators: the pattern of tracking dispatched timestamps and running a periodic zombie-detection pass (anything without a completed event older than your max expected task duration = suspected zombie) works reliably independent of the harness state. What it can't do is clean up the /workflows UI display, which is what makes this worth fixing at the source.

We run into this exact surface issue — ghost tasks accumulating in the lifecycle view — when coordinating multi-session Claude Code from an external poller. Glad you filed it with the reproduction detail.

jazzyalex · 1 month ago

Reproduces on macOS (Darwin 24.6.0), Claude Code desktop 2.1.197. Four background Agent-tool tasks survived a host-process crash and kept showing as 'Running' in the Background tasks panel with live elapsed timers (2h57m+) and stop controls. In the resumed session, TaskStop for those IDs returned 'No task found with ID: …' — the harness wasn't tracking them; the cards were pure UI zombies until dismissed manually. The misleading part: the ever-increasing timers suggested runaway CPU work, prompting the user to pkill processes that were actually fine.

kcarriedo · 1 month ago

This matches a pattern we ran into building a polling scheduler on top of Claude Code agents -- background tasks that show as "running" indefinitely because the server-side state machine never gets a reap signal after the hosting process restarts.

The two-part fix you are proposing makes sense: reap on reconnect for zombie entries, and treat the tool-result notification itself as the terminal signal on the server side. Until then the /workflows view is useless as an operational surface after any restart.

For anyone hitting this now, one workaround is keeping your own side-car ledger of dispatched task IDs with timestamps and comparing against the result-notification stream. You get durability at the cost of running two state machines.

If Anthropic wants a concrete use case for the reap-on-reconnect path: a long-running automation that restarts nightly will accumulate 20+ zombie entries in about a week of normal use, which is what this issue documents. That is a strong argument for the change.

bdemeure · 1 month ago

Hit the same symptom on Windows 11 (Claude Code CLI). Adding a detail that might help narrow the root cause: in my case the orphaned task wasn't a plain background agent — it was a general-purpose agent that had internally delegated its own work to 3 parallel sub-agents (its own Agent tool calls, "batch 1/2/3" of a file-migration task). The parent Claude Code process crashed mid-run; on restart I got a <task-notification status="failed"> saying the parent agent's in-process state was lost.

The 3 child sub-agents kept running independently afterward - visible in the UI as "En cours d'exécution" for 23h14min+, with live-incrementing elapsed time and tool-usage counters, actively still editing source files in the repo. Calling TaskStop with the parent agent's known ID returned No task found with ID: ..., so the new session had no way to reach or stop them programmatically - had to be stopped manually from the UI.

This suggests the orphaning can happen at least two levels deep (agent → sub-agents it spawns itself), which might be relevant if the fix is scoped only to top-level dispatched tasks.

kcarriedo · 1 month ago

Seeing this too. The zombie "running" entries in /workflows accumulate pretty fast when you're doing overnight multi-agent runs - after a few days the list becomes unusable as a status surface.

The TaskStop returning "No task found" with a namespace mismatch is the key clue: the task record lives server-side but the local dispatcher lost the handle. One workaround that's partly worked for us: writing the task ID to a local state file at spawn time and issuing a cleanup pass on startup that tries TaskStop against any IDs in that file that aren't in a terminal state. It doesn't handle the cross-host case you're describing though.

A few questions that would help scope the fix:

  • Does the zombie record show the correct completed_at timestamp if you fetch it directly, or is it actually missing completion metadata?
  • Is the display bug in /workflows reading from the same source as TaskStop, or is there a separate persistence layer for the UI?

If TaskStop is hitting a different namespace than /workflows, a reconciliation endpoint (or a forced-sync flag on restart) seems like the cleanest fix.

kcarriedo · 1 month ago

The zombie task accumulation is a real workflow problem - once the /workflows view fills up with 20+ historical dispatches that all show "running", the UI stops being usable for monitoring actual in-flight work.

A few things that helped in similar setups while waiting for a server-side reap:

If you are on a version that supports it, claude agent stop --all or the equivalent can batch-terminate sessions. Worth checking your current version's help output.

For the zombie records specifically - if TaskStop returns "No task found" but the UI still shows them running, the state is clearly living on the server side without a client-side way to clear it. The workaround that has worked for some people: fully sign out and back in (not just restart the client). This forces a fresh session sync and sometimes clears stale task records.

Longer term, the architectural ask you are making - either reap on reconnect or transition on result-notification - seems right. The current behavior of monotonically-growing ledger defeats the purpose of having a status view.

Showing cached comments. Read the full discussion on GitHub ↗