FleetView Ctrl+X delete leaves orphaned roster.workers entry + live worker process (row stuck as "Completed"); `claude rm` cleans it fully [v2.1.204]
Summary
In the agent view (claude agents / FleetView), pressing Ctrl+X twice to delete a session whose worker process is still alive does an incomplete teardown: it removes ~/.claude/jobs/<id>/state.json but leaves the ~/.claude/daemon/roster.json → workers[<id>] entry and the underlying worker process alive. The row then re-renders permanently as "Completed" and cannot be dismissed with further Ctrl+X. claude rm <id> performs the full teardown and clears it.
This is most reproducible with spare-pool-derived sessions (dispatch.source: "spare"), whose bg-pty-host / bg-spare processes outlive a "stop".
Environment
- Claude Code v2.1.204
- macOS (Darwin 24.x), iTerm2 (no
^Xkey remap; not inside tmux/screen; no~/.claude/keybindings.json)
Repro
- From the agent view, dispatch a new session (it claims a spare worker).
- Interact minimally so it reaches an idle/awaiting-input state.
- Select its row and press Ctrl+X twice to delete it.
Expected
The row is removed from the view (same as claude rm <id>): worker process terminated, roster entry pruned, jobs/<id>/ and sessions/<pid>.json removed.
Actual
The row flips to "Completed" and stays. Pressing Ctrl+X again is a no-op.
Root cause (on-disk analysis)
The agent view renders a session if it appears in either roster.json.workers[<id>] or has a jobs/<id>/state.json. Comparing the two delete paths on a session with a live worker:
| Record | Ctrl+X-delete | claude rm <id> |
|---|---|---|
| jobs/<id>/state.json | removed | removed |
| roster.json → workers[<id>] | left behind | pruned |
| worker process (bg-pty-host / bg-spare) | left alive | killed |
| sessions/<pid>.json | left | removed |
Because the worker process stays alive, the daemon retains its roster.workers entry, so the row keeps rendering. With jobs/<id>/state.json gone there is no "running" state, so it defaults to "Completed". The second Ctrl+X does nothing because the delete handler already ran and there is no state.json left to act on — it never re-issues the process-kill / roster-prune.
A/B evidence
For an orphaned session left by Ctrl+X (worker PIDs alive, roster.workers[<id>] present, no jobs/<id>/state.json), running claude rm <id> results in:
roster.workersno longer contains<id>- the
bg-pty-host/bg-sparePIDs are terminated jobs/<id>/andsessions/<pid>.jsonremovedclaude agents --json --allno longer lists it
So the only functional difference is that claude rm instructs the daemon to terminate the worker and prune the roster entry, which the Ctrl+X-delete handler omits.
Suggested fix
Make the agent-view Ctrl+X-delete action invoke the same full teardown as claude rm: terminate the worker process and prune roster.workers[<id>] (in addition to removing jobs/<id>/state.json), especially for dispatch.source: "spare" sessions whose PTY-host processes survive a stop.
Workaround
Use claude rm <id> instead of Ctrl+X for such sessions.