Repository navigation
Session lifecycle: cleanup of unused detached sessions, review, protection, End All Eligible (stacked on #542) - #544
raiseCatError wants to merge 5 commits into
Conversation
…ions With several detached sessions, Enter resumes the highlighted session (or the selected ones) instead of starting a new one; N or Esc starts new. X kills the selection or the highlighted session after a confirmation that names the count and lists them, so A then X is Kill all; sessions attached in another window are never touched and each outcome is reported. Rows and the one-session prompt show name, directory, shell and the same evidence based status as /sessions. The picker redraws on resize. Nothing about launching or leaving NMSh ends a session.
The one-session prompt treated a kill the service refused (attached in another window) as done and swallowed failures; it now returns to the prompt with the reason. Ending a session goes through one function, endDetachedSession: an unreachable service is a failure (it used to read as "already ended"), a refusal because a window attached meanwhile is "not touched", and a shell this window ended whose archive failed is reported as ended but not archived yet, with the spool kept. The service refuses to attach a session between its confirmed kill and the shell's exit. Bulk confirmations list every target by name, and the outcome line puts failures first.
…nd All Eligible The service decides from facts it observed, through one pure function (src/session/lifecycle.ts) that the picker also reads. A detached session that never ran a command ends after a grace period (Config -> Sessions -> Clean up unused sessions; 10 minutes by default), after the service re-verifies right before that it is still detached, unused, unprotected, holds no command, input wait, queue, task or job, and has no child processes (pgrep -P on its own unreaped PTY child; unreadable keeps it). The spool records why, and the archived transcript says so. Used idle sessions are only suggested for review after a day; idle expiry is opt-in. Attached, working and waiting sessions are never ended by a timer, and sessions created before a policy reached the service keep their old behaviour. The policy travels in each hello, bounded; protect and kill when=eligible go only to services that advertise 'lifecycle'. One timer per detached session, armed on detach, prompt, policy or protection change and cleared on attach, exit and end. The startup picker counts working, waiting and idle sessions, tags cleanup status, and adds E End All Eligible (exact list; the service re-verifies each and keeps any that changed), I Review Idle Sessions, P Protect and C cleanup settings. An explicit end of a shell that ignores SIGHUP now completes: SIGKILL to the same child after 3 s.
…k; say idle age once The two Session Lifecycle settings are listed in Setup's Sessions & alerts section, as every Settings row must be. A cleanup re-check that finds no child processes but a changed session re-arms the ordinary schedule instead of waiting an hour. Picker rows no longer repeat the idle age the activity column already shows.
- A shell still in its startup (an rc file waiting on a question, or exec'ing tmux) or holding a fullscreen program counts as busy, never as an unused session to clean up. - An unknown kill condition is refused instead of read as an unconditional kill. - Sessions chosen by Review Idle Sessions are ended through the service's eligibility re-check (child processes included), as E is. - Escalation to SIGKILL confirms the pid is still this service's own child first: node-pty reaps before it reports the exit. - Only sessions created by a connection whose own hello stated a policy can be ended by a timer; another window's policy never reaches an older frontend's sessions. - A shell switch during a child-process read is detected; lifecycle facts use one clock; protected sessions arm no timer.
Verification at c7a749eFocused tests (all pass)
Crash, restart and orphaned service state, and stale claim PIDs, are covered by the existing Real Rex check (isolated NMSh: own HOME, config, data and NMSH_RUNTIME_DIR)
No other Rex or NMSh session was touched.
|
Session lifecycle: event-driven cleanup of unused detached sessions, with review, protection and End All Eligible in the existing startup picker.
Stacked on #542 (branch
fix/startup-sessions-20261111, b63cc3b). Review and merge #542 first; this PR's diff againstdevincludes #542's two commits until then.What NMSh does today (investigated)
The per-user service
nmshdowns every PTY and shell. A window is a client that controls at most one session. Rex's persistence and NMSh's are separate lifecycles: NMSh never reads intent from a host pane.detached, its shell stayed alive, and the unused countdown started.rex kill)onTerminatesends detach./exit,/zsh,/fish,/bashnmshfrom that shell returns to it.Policy
One pure function,
eligibility()insrc/session/lifecycle.ts, decides from facts the service observed: attached, ending, protected, running, waiting for input, queued commands, background tasks, shell jobs, child processes, used, detached-since, last activity. The service applies it, and the picker shows the service's answer.pgrep -Pon the service's own PTY child; unreadable means keep). Transcript archived with the reason.busy)Upgrades: the policy travels in each frontend's hello. Only sessions created by a connection whose own hello stated a policy can ever be ended by a timer. An unknown
killcondition is refused, never read as unconditional (unused=<s>;idle=<s>, bounded on both sides). An older frontend states none and changes nothing. Older services ignore the field, and the newprotectandkill when=eligiblerequests are only sent to services that advertise the newlifecyclefeature. A configuration without the new keys gets unused cleanup at 10 minutes and idle expiry off.Updates are event driven. Each detached session has at most one timer, armed on detach, prompt, policy change or protection change, at the moment it could become eligible, and cleared on attach, exit and end. A detached session holding work is looked at again at most hourly. Nothing polls the process table.
Picker (extended, no new manager)
/exit. End is always an explicit, confirmed X or E.Settings: Config → Sessions → Clean up unused sessions and End idle sessions (advanced).
Commits
Automated verification (head c7a749e)
sessionLifecyclePolicy,sessionLifecycleService(14 tests with real shells and isolated runtime dirs),startupLifecyclePicker,startupKillSafetyandstartupRestore. The startup-busy test fails without its fix.npm run verify: build ok, 4,584/4,623 pass, 25 failures in 20 files. Load average was 30–60 from other sessions' suites./Volumes/T7Tmp/ai-dev/nq/evidence/session-lifecycle/(see README.md there). The combined tree still needs the orchestrator's sequential full verify.rex killonly detach; the real 10-minute cleanup fired 1 s after its deadline and archived with the reason.Unresolved risks
pgrepis unavailable or times out, nothing is cleaned automatically.Needs human terminal testing
Use throwaway sessions only:
nmsh --newin a pane, close the pane;nmsh --sessionslists it detached; reopen within 10 min and reattach (no cleanup); close again and leave it 10+ min; it ends and/resumeshows the archived transcript with "Ended by NMSh session cleanup".sleep 3600, one atread x, one withsleep 3600 &: none end after 10 min.