/exit warns about running background agents when none are running; agent sidecars never record completion

Open 💬 0 comments Opened Jul 4, 2026 by arcavenai

Environment

  • Claude Code 2.1.200
  • macOS (Darwin 25.3.0), arm64
  • Install path: ~/.local/bin/claude

---

Summary

After spawning any subagent via the Agent tool during a session, /exit warns that background agents are still running, even when every spawn was foreground (run_in_background: false, the default) and every result has already been returned to the main loop. Users have no way to see, list, or stop the agents the warning is referring to — they are, by every observable signal, already done.

The root appears to be that per-agent metadata written under ~/.claude/projects/<project>/<session>/subagents/agent-*.meta.json is written once at spawn and never updated. There is no status, endedAt, exitCode, completed, or any other terminal-state field, so nothing on disk distinguishes "returned six hours ago" from "still executing."

---

Reproduction

  1. Start a fresh session in any project.
  2. Use the Agent tool once (defaults — foreground, run_in_background: false). Any agent type; any prompt. Wait for the tool result to return in the transcript.
  3. Type /exit.

Expected: clean exit, no warning (nothing is running).
Actual: warning that background agents are still active, with a count.

The count grows with every additional Agent call in the session, regardless of whether any of them were ever backgrounded.

---

Evidence from an affected session

Inspecting ~/.claude/projects/<project>/<session>/subagents/:

  • 102 agent-*.meta.json sidecar files present.
  • 102 matching agent-*.jsonl transcript files present.
  • 0 foreground/background distinction on disk — the tool argument run_in_background is not reflected in the sidecar.
  • 0 files contain any terminal-state field.

Schema across all 102 files:

agentType: 102
description: 102
spawnDepth: 102
toolUseId: 85           # anonymous Agent() spawns
name: 17                # named/addressable spawns
taskKind: 17            # "in_process_teammate"
teamName: 17
color: 17
planModeRequired: 17
model: 17
permissionMode: 17
Terminal-state fields present: False

Representative meta contents (an agent whose result was returned to the main loop hours before /exit):

{
  "agentType": "<some-agent-type>",
  "description": "<one-line task summary>",
  "toolUseId": "toolu_bdrk_...",
  "spawnDepth": 1
}

Same shape for every agent in the directory. Nothing on disk changes when the agent finishes.

---

What appears to be broken (one or more of these)

  1. Sidecar metadata never records completion. .meta.json is write-once at spawn. Whatever check /exit uses to compute the "running" count has no ground truth to consult.
  1. /exit's "is running" filter is wrong. Even if terminal state lives somewhere else (in-memory only, or in the top-level session JSONL), the exit warning is counting sidecar files (or an equivalent addressable-agent set) rather than actual live process/task handles.
  1. User has no surface to inspect or reconcile. There is no visible list of "these agents are believed to be running" and no command to clear the phantom set. The warning cannot be acted on; users are left choosing between ignoring it (and risking real stranded work someday) or force-quitting.

---

Secondary observation — unbounded sidecar growth

Because completion is never recorded and sidecars are never pruned, subagents/ grows without bound across the life of a session. Long-lived sessions accumulate hundreds of transcripts totaling ~100MB+, indistinguishable to any tool from live work. This makes any "running count" that treats these files as authoritative permanently wrong.

---

Suggested direction (non-prescriptive)

Any one of these would resolve the false positive:

  • Update the sidecar (or write a companion .done marker) when the Agent tool returns its result to the parent.
  • Have /exit check active process/task handles instead of persisted sidecar files.
  • Expose a /agents or /tasks command that lists whatever /exit believes is running, so users can see and reconcile the discrepancy.

The first two prevent the false positive; the third at least makes it debuggable.

View original on GitHub ↗