Skip to content

Startup picker: distinct Resume, New and Kill; counted kill confirmations - #542

Draft
raiseCatError wants to merge 2 commits into
devfrom
fix/startup-sessions-20261111
Draft

raiseCatError wants to merge 2 commits into
devfrom
fix/startup-sessions-20261111

Conversation

@raiseCatError

@raiseCatError raiseCatError commented Nov 11, 2026 •

Copy link
Copy Markdown
Owner

Startup session picker: clear Resume, New and Kill, with counted confirmations.

What changed and why

The startup picker (shown when NMSh starts and detached sessions exist) mixed up resuming and starting new. Enter with nothing selected started a new session, and there was no way to kill several sessions at once. This PR improves the existing picker. /sessions and /resume are unchanged, and nothing is added alongside them.

  • Resume, New and Manage are distinct.
    • Enter resumes the highlighted session, or the selected ones.
    • N or Esc starts a new session.
    • Space and A select. X kills.
    • The footer says what Enter and X will act on right now: "resume this" or "N selected"; "kill this" or "N selected".
  • Kill this / Kill selected / Kill all.
    • X opens a confirmation that names the count ("Kill 3 detached sessions?") and lists every target by name, directory, shell and status. There is no "and N more": the service caps live sessions at 16, so the list is bounded.
    • Enter or Y confirms; Esc or N cancels.
    • Targets are killed one at a time. One line reports the result for each, including sessions that were already gone.
    • Sessions attached in another window are never killed. They stay listed and are deselected, as before.
  • Identity.
    • Each row and the single-session prompt show the session's name (or signature), directory and shell.
    • Status comes from the same evidence-based liveStatusParts as /sessions, such as "idle 1m" or "running …".
    • Only what the service reports is shown; nothing is guessed.
  • Narrow terminals. The picker redraws on resize.

Safety review fixes (b63cc3b)

An adversarial review of the bulk kill found these problems; each is fixed with a regression test in tests/startupKillSafety.test.ts.

  • The one-session prompt treated a refused kill as done. If the session became attached in another window during the confirmation, askWithKill still returned "kill", and startup went on as if it had ended. Errors were also swallowed silently. It now returns to the prompt with the reason.
  • An unreachable service read as "already ended". The frontend listed sessions with .catch(() => []), so a service that timed out made every target look gone. A listing failure is now a failure for that session.
  • A kill that worked but failed to archive was reported as "had already ended". It is now reported as ended, with the transcript not archived yet. The spool is kept and the next launch archives it.
  • Attach racing the kill. If a window attached between the re-read and the kill, the service's refusal showed up as a generic failure. It is now "attached in another window, not touched".
  • Attach after the kill. Between the kill and the shell's exit event, another window could still attach to the dying session. The service now refuses that attach.
  • Not every target was named. Bulk confirmation rows had no session name, so two sessions in ~ looked the same, and targets after 6 were collapsed into "and N more". Every target is now named.
  • The outcome line puts failures first, so a narrow screen never clips them.

All of these go through one function, endDetachedSession (src/session/liveSessions.ts). It re-reads the service's list, addresses the kill by the service's session id (never a PID), and reports only what the service proved. The service still re-checks attachment atomically when it receives the kill. Draft PR #544 builds on this branch: it adds the session lifecycle and cleanup policy. Merge this PR first.

  • Unchanged. Launching or exiting NMSh never kills a session. Leaving the picker keeps every session running, and the footer says so (/resume).

Automated verification

At b63cc3b:

  • tests/startupKillSafety.test.ts (new, 7 tests): every kill outcome; a stale, gone or attached target; partial failures; exact targets; the one-session prompt; and a real-service test that attach is refused while a kill is pending. That last test fails without the fix.

  • Every tests/startup*, tests/session* and related session test file (248 tests): the only failures were live-frontend tests timing out at about 20 s while the machine was under heavy load (load average 27–36 from other sessions' suites). The four files with failures, run side by side on 4aeb848 and b63cc3b under the same load, failed the same 15 tests on both, so this commit adds no failure there.

  • GitHub CI run 43065468653 on b63cc3b: every job succeeded, covering Detect, Quality (Node 26), Node 22 compatibility, Tests ubuntu-24.04 1–3/3 and macos-latest 1–3/3, CI, and Verify 22.x/26.x. Fedora and the timing smokes are dispatch-only and were skipped.

  • Logs: /Volumes/T7Tmp/ai-dev/nq/evidence/session-lifecycle/ (see README.md there). The timeouts are not proven to be load: they also occur on the original head under the same conditions, and hosted CI passes them.

Unresolved risks

Earlier, at 4aeb848:

  • tests/startupRestore.test.ts and tests/startupPickerHostile.test.ts: 21/21 pass. Two new tests cover multi-kill with a count, Kill all with a mixed result tally, and rows showing shell and status.
  • npm run verify: 4,567 of 4,586 tests pass before the final wording fix. Of 5 failures, 4 also fail on pristine origin/dev on this Mac (GNU screen interop, live alias refresh, text editor panel, suite-runner tmpdir hygiene). The fifth (startupDiscovery: "idle at the prompt") was this change: fixed by keeping that wording on the one-session screens. startupDiscovery, startupRestore and startupPickerHostile then pass 27/27.
  • All tests use fixture sessions with stub kill functions. No real session was killed.

Needs human terminal testing

In Rex, Ghostty and Terminal.app, with 2–4 throwaway detached sessions (nmsh --new, then close the window):

  1. Start NMSh and check the picker rows (name, directory, shell, status).
  2. Enter resumes the highlighted session; N starts new.
  3. Space-select two sessions, press X, check the count in the confirmation, then Esc. Nothing is killed.
  4. Press A then X and confirm. All detached sessions are killed, and a window's attached session is untouched.
  5. Resize to about 60 columns while the picker is open.

…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.

This branch has not been deployed

No deployments
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.

1 participant