0105 — Sessions can outlive their last window
A session may be marked keep-empty, and a keep-empty session is not reaped when its last window closes; only an explicit kill removes it.
Full source summary
A session may be marked keep-empty, and a keep-empty session is not reaped when its last window closes; only an explicit kill removes it. A session created with no seed terminal is keep-empty by construction. Default sessions keep today's tmux cascade, so the "exit closes everything" habit and the ADR-0063 self-exit rule are untouched. Clients render an empty state instead of detaching.
Status: Accepted Date: 2026-09-10
Context
The server reaps pane -> window -> session as one cascade
(phux-server/src/state/reap.rs, phux-60s): the session goes with its last
window, and the server self-exits once it holds no sessions after serving a
client (tests/lifecycle/server_self_exit.rs, ADR-0063). Every create path
seeds a terminal, including phux.session.create/v1 (docs/spec/L3.md), and
the TUI detaches when a fold empties its workspace (phux-4r1, consumer-owned
policy under ADR-0015).
So a session cannot exist without a running process. That blocks two flows
from the Superlogical reconstruction
(research/2026-09-09-superlogical-demo/GAP.md, F16 and F17): creating a
session from a CLI and filling it later from a GUI, and treating “close the
last tab” as different from “kill the session”. Cockpit already keeps
workspaces across close on the client side, but a session only one client
remembers is invisible to the CLI and to every other client.
Decision
- Keep-empty is a per-session property. It is set at creation (a
keep_emptyfield onphux.session.create/v1) or later through a metadata key, and it is reported inlsand session listings. Sessions without it reap exactly as today. - A create with no seed terminal makes a keep-empty session.
phux new --emptyand the same request withcommandomitted andempty: truecreate a session with zero windows, since an empty session that reaps immediately would be meaningless. - The cascade stops at a keep-empty session.
reap_window_if_emptyremoves the window and keeps the session. Removal is explicit only:phux kill SESSIONorKILL_RESOURCESgroup teardown. - Server lifetime still counts sessions. A keep-empty session holds the
server up with zero processes, the same way an idle one does today.
--exit-after-idleremains the lever for ephemeral servers. - Clients show an empty state and do not detach. When the last pane of a keep-empty session closes, the TUI and Cockpit render “Empty session” with a New Window action, keeping the attach alive. Default sessions still detach.
The wire change is a new optional field and one metadata key. It lands with the implementation under the usual spec, CHANGELOG, and version-bump rule.
Why
Opting in per session is the only option that gives CLI/GUI-coherent
durable sessions without changing what exit means for everyone else.
Existing scripts, e2e tests and ADR-0063 harnesses depend on the cascade
tearing down a server when its last shell exits. Making the property part of
the session, not client configuration, means every client and the CLI agree
on whether the session still exists.
Tradeoffs
- A server can live with zero processes.
lsmust mark empty sessions, or users will not see why the daemon is still running. - Every surface gains a state it must render: the TUI, Cockpit, the
web client and the agent verbs (for example,
phux waitagainst an empty session). - Two lifecycles to document and test instead of one.
Alternatives
Every session is durable. This is simplest to explain, but it breaks tmux muscle memory and the self-exit contract. Servers would effectively never exit on their own.
A global config switch. One setting cannot let a user keep a long-lived project session alongside throwaway ones, and a setting that differs between machines makes scripts behave differently.
Client-side placeholders only. Cockpit already does this. The CLI cannot
see such a session, so phux new from a shell could never produce one a GUI
opens later.