claude agents --json reports state: stopped, but the state set is undocumented and has no terminal flag
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
- A boolean on each entry —
terminal, orfinished— 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. - Document the full state enum, including which values are terminal.
- 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.