Skip to content

feat(sessions): /quit or 'stop' parks a session instead of respawning it - #213

Merged
lionello merged 1 commit into
masterfrom
feat/session-stop-167
Aug 12, 2026
Merged

feat(sessions): /quit or 'stop' parks a session instead of respawning it#213
lionello merged 1 commit into
masterfrom
feat/session-stop-167

Conversation

@defangdevs

Copy link
Copy Markdown
Owner

Closes #167.

Motivation

Sessions respawn automatically whenever their tmux session dies — the right behavior for a Spot cycle, an OS reboot, or a crash, but it also swallowed every intentional exit: typing /quit into a claude session killed the TUI, and ~2s later the supervisor respawned it with --resume. There was no way for a client (Remote Control, the CLI, an agent managing its own helpers) to close a session for good short of the web UI's delete or agent-box-session rm, which throw the session's config away.

What changed

A session entry in sessions.json can now carry stopped: trueparked: still listed, deliberately left down. Three things set/clear it:

  • Clean agent exit (exit 0) parks the session. The pane command's epilogue is now agent && mark-stopped || exec bash: exit 0 (/quit, Ctrl+D — an exit somebody asked for) records stopped: true before the pane ends. A non-zero exit still gets the post-mortem inspection bash, and a kill (restart, reboot, Spot stop) runs no epilogue — those still respawn and resume, unchanged. So /quit now does what it says, from the terminal or from a Remote Control client.
  • agent-box-session stop NAME parks a session imperatively (flag first, then kill, so the supervisor's post-spawn re-check catches the race the same way rm's delist-then-kill does).
  • restart NAME is the revive verb: it clears the flag and the supervisor brings the session back (resuming its transcript as before). restart --all revives everything. The settings page's Restart button does the same.

The supervisor's reconcile loop skips stopped entries; its pre-/post-spawn delist re-check now also covers the stopped flag.

Two session kinds opt out of park-on-clean-exit:

  • codex + remoteControl: that pane runs the RC daemon supervisor wrapper, whose health loop falls off its end with status 0 when the daemon dies — a crash to self-heal by respawning, not a quit somebody asked for (nothing interactive in that pane can even ask to quit). Old semantics kept.
  • shell: exiting a shell still hands you a fresh one, as documented; park one with stop instead.

mark-stopped is a verified write with retries: on an agent that exits within its first seconds, the supervisor's mark_started rewrite can race it (both are tmp+mv), so it re-reads until the flag stuck, and never re-creates a session delisted meanwhile.

User-visible effects

  • /quit (or any clean agent exit) closes the session instead of resuming it.
  • agent-box-session stop NAME; ls shows a stopped state; restart revives (and now tolerates a session with nothing live to kill instead of erroring).
  • Settings page and workspace show a grey stopped badge; the workspace pane placeholder says the session is stopped rather than promising it is "starting…"; session_counts excludes stopped sessions so the "Restart all" spinner can't hang on one.
  • The canonical AGENTS.md guide documents the new lifecycle.

No security-relevant surface changes: the new mark-stopped helper writes only the user's own sessions.json (same tmp+mv discipline as the existing writers), and the session name it receives is printf %q-quoted into the pane command like every other runtime field.

Checks run

  • nix run .#assemble + module-generated-up-to-date (aarch64-linux)
  • nix run .#update-golden + golden-snapshot — fixture diff is the supervisor/session-CLI/settings payloads plus the new mark-stopped payload per user
  • multi-user, module-single-file, download-route, webhook-route (aarch64-linux)
  • tests/sessions.nix extended (runs in CI, x86_64-only): clean exit parks (claude --help as the exit-0 stand-in), stop parks, restart revives and clears the flag
  • Premise spot-checked on a live box: claude --dangerously-skip-permissions --remote-control x --help exits 0

🤖 Generated with Claude Code

https://claude.ai/code/session_01SDHb5tyTqZwzKM6Qdr8cxv

…es it

/quit (and any exit-0 agent exit) used to be respawned-and-resumed
within ~2s, and nothing short of rm/delete could close a session for
good. A stopped:true flag in sessions.json now parks a session — still
listed, deliberately down: set by the pane epilogue on a clean agent
exit and by the new 'agent-box-session stop NAME'; cleared by restart
(CLI or settings page), which becomes the revive verb. Crashes keep the
post-mortem bash, kills/reboots/Spot stops keep respawn-and-resume, and
codex RC daemon + shell sessions keep their old semantics (a dying RC
daemon exits 0 and must self-heal, not park).

Closes #167

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDHb5tyTqZwzKM6Qdr8cxv
@lionello
lionello merged commit fb7bb43 into master Aug 12, 2026
1 check passed
@lionello
lionello deleted the feat/session-stop-167 branch August 12, 2026 23:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Need a way to quit (no restart) non-main session

2 participants