Skip to content

(remote): stop a remote session for real, behind the same dialog as a local one - #272

Merged
devsuitup merged 2 commits into
mainfrom
feat/remote-stop
Sep 11, 2026
Merged

devsuitup merged 2 commits into
mainfrom
feat/remote-stop

Conversation

@devsuitup

Copy link
Copy Markdown
Owner

Lifecycle verbs decided on #246 (2026-09-11): detach closes the view, stop terminates the process — for remote rows too, behind the same control and the same confirmation dialog as a local one. #271 tracks the bulk archive/delete sites.

What

  • remote-stop.js: buildStopCommand(pid, tmuxTarget) and createRemoteStopAdapter().stop(alias, descriptor). The command runs the pid-reuse guard first (buildProcCmdlineCheck from remote-attach.js, same wording: a pid that no longer names a claude CLI is refused, nothing is killed), then the narrowest tmux scope — kill-pane -t <target> when the target names a pane, kill-window when it names only a window, never kill-session (several CLIs share one tmux session on the VPS) — else kill -TERM, poll /proc/<pid> for ~3 s, kill -KILL once. Returns { ok, method: 'tmux-pane'|'tmux-window'|'pid-term'|'pid-kill', error }. Pid is integer-validated, target regex-validated (TMUX_FIELD_RE), alias is its own argv element; no backticks.
  • main.js: IPC remote-stop-session({ alias, sessionId }); on success drops the descriptor (remote-index.js dropRemoteSession), forces refreshHostNow(alias), and closes the local PTY holding the attach if any. preload.js: remoteStopSession.
  • public/stop-session-ui.js: pure resolveSessionStop(session) → which IPC and which dialog text (host alias shown for remote); app.js confirmAndStopSession routes through it. One dialog component. Closing a remote tab stays a detach ((remote): make a solo tmux attach look and behave like a local terminal #256 restore path untouched).
  • remote-activity-ui.js applyRemoteStopped: liveness:'dead', attached:false, timers purged.
  • Docs: ipc-bridge.md, session-state.md (verbs section), docs/session-browser.md.

Proof

  • test/remote-stop.test.js (16): guard runs before either path (mutation: guard emptied → red); pane → kill-pane, window → kill-window; kill-session never appears for any input (mutation: forced back → 3 red); adapter with a fake runner: success, refusal, ssh failure.
  • test/stop-session-ui.test.js: local → stopSession, remote → remoteStopSession with the alias in the dialog text. remote-index, remote-activity-ui, terminal-manager-lifecycle additions.
  • task check: 1255 + 119 pass, 0 fail, pre-existing skips.

Not verified on the wire: the kill command against the real host (tests only). Next: #271.

… local one

Part of #246 (lifecycle verbs). A remote row's stop control now goes through
remote-stop-session: the descriptor pid is re-probed on the host (refused if
it no longer names a claude CLI), then the narrowest tmux scope is killed
(pane, else window — never the shared session), else TERM then KILL by pid.
Closing a remote tab stays a detach. The descriptor is dropped and the host
refreshed at once; the adapter marks the row dead.
…led stop

Review follow-up on the remote stop. The projection marks a row whose process
is alive (.is-alive) and the stop control shows for it, attached or not; a
failed stop leaves activePtyIds and the open view untouched and flashes the
button instead of logging only; the tmux kill path polls /proc before
claiming success and falls through to the pid path; the main handler's
ok-guarded fan-out is pinned by a source check.
@devsuitup

Copy link
Copy Markdown
Owner Author

Review follow-up pushed (second commit):

  1. CRITICAL — the projection now writes .is-alive on a row whose liveness is alive; the stop control shows for .has-running-pty, .is-alive in the sidebar and under the same rule in the grid. Mutation: removing .is-alive from the show rule turns the test red.
    2/3. MAJOR — confirmAndStopSession returns before touching activePtyIds / the open view when result.ok is false, flashes the clicked button (Failed, 1500 ms) and puts the error in its title for 3 s; sidebar, grid and tab call sites pass the button. Mutation: restoring the unconditional delete turns the test red.
  2. MAJOR — buildDeathPoll(pid) shared by both paths: after kill-pane/kill-window the command polls /proc/<pid> before echoing the success marker and falls through to TERM → poll → KILL if the target survives. Mutation: dropping the post-tmux poll turns the test red.
  3. MAJOR — source check in test/main-wiring-source-check.test.js: dropRemoteSession, refreshHostNow(... force) and killPty all inside if (result.ok), none before it. Mutation: moving the drop out of the guard turns it red.
  4. MINOR — ipc-bridge.md method enum fixed.
  5. MINOR — pane-suffix assumption documented in session-state.md; the death poll bounds the blast radius if it is ever absent.

Deferred, documented under "Known limits" in session-state.md: the grep -qi claude cmdline guard is weak for a kill; candidates are the comm field of /proc/<pid>/stat and the start time.

task check: 0 errors, full suite green.

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