feat(minimald): lifecycle management (run --detach, status, stop) - #435
feat(minimald): lifecycle management (run --detach, status, stop)#4350chroma wants to merge 3 commits into
Conversation
- Add CLI subcommands: activate, attach, destroy, dash (fzf picker stub) - Implement SSH client transport over UDS to minimald - Wire oneshot RPC calls for ls (list sessions), activate (create session) - Add exec_in_session for non-interactive command execution in sessions - PTY interactive shell is blocked on daemon PTY support (exec.rs:650-654) - Update Cargo.toml with minimald-rpc, russh, sessions, serde_json deps
- ls --raw: outputs one session ID per line for fzf piping - minimal dash: interactive fzf picker that chains into attach - Falls back to plain table if fzf is not installed - Update implementation plan with Phase 5 details
Phase 1: lifecycle state machine (crates/minimald/src/lifecycle.rs) - Pure state machine: NotProvisioned/Stopped/Starting/Running/Stopping - Table-driven next_state() with exhaustive unit tests - TransitionError for invalid transitions Phase 2: state persistence (crates/minimald/src/state.rs) - State struct: lifecycle, pid, started_at - StateDir: read/write atomic state.toml, lifecycle.lock (fd-lock) - StartingGuard: RAII guard that resets to Stopped on drop - 10 unit tests: round-trip, missing-file, lock contention, guard semantics Phase 3: run --detach (crates/minimald/src/cmd/run.rs) - MinimaldRunArgs: --detach, --detach-timeout (default 4s) - run_detach(): spawns child, writes pid, polls UDS, transitions Running - Foreground run: StartingGuard, lifecycle transitions on startup/shutdown - Graceful SIGTERM/SIGINT handling Phase 4: status and stop subcommands (cmd/status.rs, cmd/stop.rs) - minimald status: human-readable + --json, exit 0/1/2 - minimald stop: SIGTERM → SIGKILL escalation, idempotent - 13 unit tests: JSON output, missing-file, running state, lock contention Phase 5: socket path + auto-spawn on Linux (crates/minimal2) - Fix resolve_socket_path() to match minimald's listen_on() path - Auto-spawn: check UDS, run 'minimald run --detach' if needed - 4s UDS poll timeout All 54 tests pass (53 minimald + 1 minimal2).
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Free Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (14)
📝 WalkthroughWalkthroughThis PR implements full daemon lifecycle management for Changesminimald Lifecycle Management
Sequence Diagram(s)sequenceDiagram
rect rgba(70, 130, 180, 0.5)
note over minimal2,minimald: Auto-spawn + connect
end
participant minimal2
participant autospawn
participant StateDir
participant minimald
minimal2->>autospawn: ensure_minvmd_running()
autospawn->>StateDir: read_state()
StateDir-->>autospawn: Stopped / NotProvisioned
autospawn->>minimald: spawn "minimald run --detach"
minimald->>minimald: setsid, bind UDS, write state(Running)
minimald-->>autospawn: process exits (detached)
autospawn->>autospawn: poll_uds_ready(socket)
autospawn-->>minimal2: Ok(())
minimal2->>minimal2: Client::connect(socket_path)
minimal2->>minimald: russh handshake over UnixStream
minimald-->>minimal2: auth success
minimal2->>minimald: oneshot_rpc(CreateSession / ListSessions)
minimald-->>minimal2: JSON response
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
Note 🎁 Summarized by CodeRabbit FreeYour organization has reached its limit of developer seats under the Pro Plan. For new users, CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please add seats to your subscription by visiting https://app.coderabbit.ai/login.If you believe this is a mistake and have available seats, please assign one to the pull request author through the subscription management page using the link above. Comment |
On Linux, check if the daemon UDS is connectable before each command. If not, spawn `minimald run` as a detached background process (setsid + null stdio) and poll the UDS until it becomes available (4s timeout). Also fixes resolve_socket_path() to match minimald's listen_on() on Linux ($XDG_STATE_HOME/minimal/providers/local-0/ssh.sock), which was previously broken — it only worked on macOS via minvmd. No state machine or state.toml — just socket polling. The lifecycle management PR (#435) can layer richer state tracking on top later.
…destroy) (#434) * feat(minimal2): implement client interface with SSH transport (#159) - Add CLI subcommands: activate, attach, destroy, dash (fzf picker stub) - Implement SSH client transport over UDS to minimald - Wire oneshot RPC calls for ls (list sessions), activate (create session) - Add exec_in_session for non-interactive command execution in sessions - PTY interactive shell is blocked on daemon PTY support (exec.rs:650-654) - Update Cargo.toml with minimald-rpc, russh, sessions, serde_json deps * feat(minimal2): add --raw flag and 'dash' fzf session picker (#159) - ls --raw: outputs one session ID per line for fzf piping - minimal dash: interactive fzf picker that chains into attach - Falls back to plain table if fzf is not installed - Update implementation plan with Phase 5 details * feat(minimal2): interactive attach via ssh ProxyCommand (#159) Implement twitchyliquid64's suggestion: shell out to ssh for interactive PTY attachment instead of reporting it as unsupported. The daemon's shell_request handler already mints PTY-backed session shells, so we avoid reimplementing termios/PTY management by deferring to openssh. Adds a hidden `proxy` subcommand that pipes stdio to the daemon UDS, used as ssh's ProxyCommand so we don't depend on socat or nc being installed. Also fixes pre-existing fmt/clippy failures. * feat(minimal2): wire up destroy command via DestroySession RPC (#159) Wire cmd_destroy to the DestroySession RPC added in #462. Resolves the session by UUID or name via GetSessionRecord (matching cmd_attach), then issues the destroy RPC and prints confirmation. * refactor(minimal2): migrate attach --command to ssh shell-out (#159) Per reviewer feedback, exec_in_session used the daemon's old exec codepath which doesn't hit the sessions module. Remove it entirely and shell out to ssh for both interactive and --command attachment — ssh handles termios/PTY reconfiguration and the daemon's shell_request handler mints the session shell. exec_request remains on the daemon side for git-receive-pack and vscode remote only. * feat(minimal2): auto-spawn minimald on Linux (#159) On Linux, check if the daemon UDS is connectable before each command. If not, spawn `minimald run` as a detached background process (setsid + null stdio) and poll the UDS until it becomes available (4s timeout). Also fixes resolve_socket_path() to match minimald's listen_on() on Linux ($XDG_STATE_HOME/minimal/providers/local-0/ssh.sock), which was previously broken — it only worked on macOS via minvmd. No state machine or state.toml — just socket polling. The lifecycle management PR (#435) can layer richer state tracking on top later. * fix(minimal2): thread --minimal-dir through auto-spawn Auto-spawn was checking the default UDS path instead of the --minimal-dir override, causing it to fail when the daemon was already running with a custom state dir. Also pass --minimal-state-dir to the spawned minimald so it listens on the same path we're polling. E2E verified: ls, activate, attach --command, destroy (by UUID and name), auto-spawn (default and --minimal-dir paths). * refactor(minimal2): remove temporary dash/fzf picker Remove the `dash` subcommand, `ls --raw` flag, and `LsArgs` struct. These were temporary scaffolding for an fzf-based session picker — a proper TUI will be built later. Net -125 lines. * revert(minimal2): add back ls --raw flag --raw has standalone value for scripting (e.g. `minimal ls --raw | fzf`), independent of the removed dash subcommand. * style(minimal2): add missing blank line between functions * feat(minimald,minimal2): detect starting daemon via PID file minimald now writes a PID file at startup. The minimal2 auto-spawn logic checks this file before spawning: if the PID is alive the daemon is already starting (e.g. spawned concurrently), so it waits for the UDS rather than spawning a duplicate. The spawn path uses Child::try_wait to detect a crash during startup and fail fast instead of exhausting the 4s timeout. Addresses review feedback on #434.
|
gonna close this since this was a real rough pass and it seems like we're gonna worry about more complex lifecycle stuff later |
Equips
minimaldwith daemon lifecycle management matching the pattern established byminvmd.Plan:
plans/2026-06-16-minimald-lifecycle-management-v1.mdWhat's Included
next_state()and exhaustive testsState,StateDir,StartingGuard— atomic state.toml, fd-lockrun --detachstatus/stopresolve_socket_path()to matchminimald'slisten_on(); Linux auto-spawn inminimal2Verification
minimald+ 1minimal2)minimald run(foreground) behavior unchangedminimald run --detachspawns in background, returns after UDS accepts connectionsminimald statusreturns human/JSON output with correct exit codes (0/1/2)minimald stopterminates gracefully, idempotent, handles already-stoppedNotes
minimalddoesn't spawn automatically on Linux throughminimal2— the auto-spawn code checks for it via UDS probe (theminimaldbinary must be inPATHon Linux)minvmdlifecycle pattern: The lifecycle, state, status, and stop modules are deliberately structured to mirrorminvmdfor consistencySummary by CodeRabbit
Release Notes
New Features
statusandstopsubcommandsrun --detachmode for background daemon executionactivate,attach,destroy, anddashcommands for session managementBug Fixes