Sessions spawned while testing clean themselves up

--throwaway-sessions, on by default in a debug build. Every session a
server started with it spawns is marked throwaway in the config, and a
marked session's process is stopped when the server exits or is
signalled, rather than left running for the next start to adopt.

Leaving processes running is the design and it is right for the sessions
somebody is using. It is exactly wrong for the ones a test made: those
leave a claude behind that every later server adopts, and nothing ever
says they are there -- twelve accumulated on this machine in a day, each
holding a conversation open.

The flag marks; the mark decides. What a server was told at startup
governs only the sessions it spawns, and the mark is the session's own,
so a session spawned deliberately keeps running whichever server is up
when one exits, and a throwaway one is cleaned away even by a server
started without the flag.

process::wait_gone does the waiting on the way out, because
process::stop leaves its SIGKILL on a tokio timer and a runtime that is
shutting down never runs it -- which is how the original shutdown_all
leaked the processes it reported stopping.

Its test found a second thing, in the same field the last commit was
about: a zombie read as Alive. /proc/<pid>/stat keeps the entry, with
the same pid and the same start time, until the exit status is
collected, so a process that had plainly finished answered "still
there" -- and Alive is the word that makes Exited unsayable, so the
session shows unknown, its Start button never appears, and Stop says
there is nothing to stop. stat_of reads the state field alongside the
start time now.

Exercised against a real server: a keeper spawned with the flag off
survives its server's exit and is adopted by the next one, a session
spawned with it on is stopped on SIGTERM within 20ms, and the
"left N running" line counts what is actually still out there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-08-30 18:41:21 -04:00
1 parent a18a57e73e
commit 06ab7343cf
7 files changed
+428 -27

No files matched your search

+24
View File
@@ -344,6 +344,30 @@ day:
no line to read a time off. Both are the same rule as
`Transcript::last_activity`: a restart has been told nothing, so it must
not claim anything happened.
- **A session spawned while testing cleans itself up: `--throwaway-sessions`**
(2026-08-30), which a **debug build defaults to on**. Every session
spawned by such a server is marked `throwaway: true` in `config.ron`, and
its process is stopped — SIGTERM, then SIGKILL after
`process::STOP_GRACE` — when the server exits or is sent SIGTERM/SIGINT.
Sessions outliving the backend is right for the ones somebody is using
and wrong for the ones a test made: those leave a `claude` behind that
every later server adopts, and they pile up unnoticed (twelve on this
machine in a day, each holding a conversation open).
Two things worth knowing. The flag decides only what **new** sessions are
marked as; what happens on the way out is decided by the **mark**, which
is the session's own — so a session you spawned deliberately keeps
running whichever server is up when one exits, and a throwaway one is
cleaned away even by a server started without the flag. And the waiting
is not optional: `process::stop` leaves its SIGKILL on a tokio timer,
which a runtime that is shutting down never runs, so
`process::wait_gone` does the waiting on the way out. Pass
`--throwaway-sessions=false` to keep what a development server spawns.
- **A process that has exited but not been reaped reads as dead**, not
alive. `/proc/<pid>/stat` keeps the entry — same pid, same start time —
until the status is collected, so a zombie used to answer "still there",
which made `exited` unsayable: the session showed `unknown`, its Start
button never appeared, and stopping it said there was nothing to stop.
`process::stat_of` reads the state field alongside the start time.
- **Each session directory now holds `process.json`, `stdin.fifo`,
`stdout.log` and `stderr.log`.** `stdout.log` is the driver's input, read
from the byte offset in `process.json`; removing either by hand while the