claude agents --json reports state: stopped, but the state set is undocumented and has no terminal flag

Status Open
Reported on v2.1.233
Maintainer reply None cached
Activity 0 comments · opened Aug 19, 2026

Environment

Claude Code 2.1.233, Linux. Background sessions started with claude --bg, listed with claude agents --json --all, stopped with claude stop <id>.

What happens

After claude stop <id>, that session appears in claude agents --json with "state": "stopped" and no pid. The states we have observed in that field are working, blocked, done and stopped, and we could not find the set documented anywhere.

That matters more than a documentation gap, because of the rule any careful consumer has to adopt. Freeing a session slot on a state you do not recognise is the more expensive mistake — it starts a second session next to one that is still running — so the safe default is an unknown state counts as still running. With that rule, stopped silently consumes capacity for ever.

Measured on our side, immediately after stopping two finished sessions: the scan reported 2 of 3 slots occupied with nothing running, both entries carrying reported_state: stopped classified as working. At a cap of three sessions the fleet was down to one usable slot, with no way back other than editing state by hand.

Why a consumer cannot just enumerate strings

done and stopped mean different things to a supervisor: a session that finished has an output worth reading, one that was stopped deliberately has nothing to decide about and must not be escalated to a judging layer. But both are terminal for slot accounting. Guessing which strings are terminal from observation is exactly how the above happened — we had done and blocked in that list and not stopped, because we had never stopped a session before.

What would help, in order of usefulness

  1. A boolean on each entryterminal, or finished — so a consumer never has to enumerate state strings to answer "is this slot free". This is the one that makes the API safe to consume as it evolves.
  2. Document the full state enum, including which values are terminal.
  3. If the set is intended to grow, say so, so consumers know to treat it as open rather than closed.

Workaround

Add stopped to the known-terminal set explicitly, and keep the "unknown counts as running" rule for everything else.

View original on GitHub ↗