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:
1 parent
a18a57e73e
commit
06ab7343cf
7 files changed
+428
-27
No files matched your search
@@ -473,6 +473,43 @@ with the list — sorted by that time — in an order that meant nothing.
|
||||
worse answer for a shared checkout that can be copied or touched;
|
||||
`SessionConfig::created` is recorded rather than inferred.
|
||||
|
||||
### Sessions spawned while testing clean themselves up (decided 2026-08-30)
|
||||
|
||||
`--throwaway-sessions`, **on by default in a debug build**. Every session
|
||||
spawned by such a server is marked `throwaway` in the config, and a marked
|
||||
session's process is stopped when the server exits or is signalled, instead
|
||||
of being left 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, they cost tokens if
|
||||
anything ever speaks to them, and nothing ever says they are there — twelve
|
||||
accumulated on this machine in a day. An agent testing this app should not
|
||||
have to remember a cleanup step, and "remember to" is not a mechanism.
|
||||
|
||||
- **The flag marks; the mark decides.** What a server was told at startup
|
||||
governs only the sessions it spawns, and the mark is written into the
|
||||
session, so it outlives that server. A session spawned deliberately keeps
|
||||
running whichever server happens to be up when one exits, and a throwaway
|
||||
one is cleaned away even by a server started without the flag. The
|
||||
alternative — the exiting server stopping whatever it happens to have
|
||||
marked in memory — makes cleanup depend on which process is up, which is
|
||||
the thing that fails at exactly the wrong moment.
|
||||
- **Stopping is not asking.** `process::stop` sends SIGTERM and leaves its
|
||||
SIGKILL on a tokio timer, and a runtime that is shutting down never runs
|
||||
it. That is precisely how the original `shutdown_all` leaked the processes
|
||||
it reported stopping, so the exit path waits for them with
|
||||
`process::wait_gone` — one deadline for all of them, since they were
|
||||
signalled together — and kills whatever is left. `Driver::stop` is the
|
||||
per-driver half, the same one a delete uses; only the waiting differs.
|
||||
- **A zombie is dead.** Found by the test for the above: `/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" for as long as nothing reaped it, and `Liveness::Alive` is
|
||||
the word that makes `Exited` unsayable. The state field is read alongside
|
||||
the start time now. This was reachable outside the test: anything that
|
||||
blocks the runtime delays tokio's own reaping.
|
||||
|
||||
### Importing refuses a session that is already open (decided 2026-08-29)
|
||||
|
||||
Claude Code keeps a descriptor per live session at
|
||||
|
||||
Reference in new issue
Block a user