Skip to content

feat(minimald): lifecycle management (run --detach, status, stop) - #435

Closed
0chroma wants to merge 3 commits into
mainfrom
0chroma/feat-minimald-lifecycle-management
Closed

feat(minimald): lifecycle management (run --detach, status, stop)#435
0chroma wants to merge 3 commits into
mainfrom
0chroma/feat-minimald-lifecycle-management

Conversation

@0chroma

@0chroma 0chroma commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Equips minimald with daemon lifecycle management matching the pattern established by minvmd.

Plan: plans/2026-06-16-minimald-lifecycle-management-v1.md

What's Included

Phase Status Summary
1. Lifecycle state machine Done Pure state machine with table-driven next_state() and exhaustive tests
2. State persistence Done State, StateDir, StartingGuard — atomic state.toml, fd-lock
3. run --detach Done Background daemon spawn, UDS polling (4s timeout), lifecycle transitions
4. status / stop Done Human + JSON output, SIGTERM→SIGKILL escalation, idempotent
5. Socket path + auto-spawn Done Fixed resolve_socket_path() to match minimald's listen_on(); Linux auto-spawn in minimal2

Verification

  • All 54 tests pass (53 minimald + 1 minimal2)
  • minimald run (foreground) behavior unchanged
  • minimald run --detach spawns in background, returns after UDS accepts connections
  • minimald status returns human/JSON output with correct exit codes (0/1/2)
  • minimald stop terminates gracefully, idempotent, handles already-stopped

Notes

  • minimald doesn't spawn automatically on Linux through minimal2 — the auto-spawn code checks for it via UDS probe (the minimald binary must be in PATH on Linux)
  • Matching minvmd lifecycle pattern: The lifecycle, state, status, and stop modules are deliberately structured to mirror minvmd for consistency

Summary by CodeRabbit

Release Notes

  • New Features

    • Added daemon lifecycle management with new status and stop subcommands
    • Implemented run --detach mode for background daemon execution
    • Expanded CLI with activate, attach, destroy, and dash commands for session management
    • Improved session listing with formatted output displaying session metadata
    • Added auto-spawn daemon support on Linux
  • Bug Fixes

    • Fixed Linux auto-spawn to properly start daemon instead of returning no-op success

0chroma added 3 commits June 16, 2026 15:13
- 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).
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: 31aa46ac-a99a-40eb-99be-18ad23e207d4

📥 Commits

Reviewing files that changed from the base of the PR and between d4e8b7d and ed1fb23.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • crates/minimal2/Cargo.toml
  • crates/minimal2/src/autospawn.rs
  • crates/minimal2/src/client.rs
  • crates/minimal2/src/main.rs
  • crates/minimald/Cargo.toml
  • crates/minimald/src/cmd/mod.rs
  • crates/minimald/src/cmd/run.rs
  • crates/minimald/src/cmd/status.rs
  • crates/minimald/src/cmd/stop.rs
  • crates/minimald/src/lib.rs
  • crates/minimald/src/lifecycle.rs
  • crates/minimald/src/main.rs
  • crates/minimald/src/state.rs
  • plans/2026-06-16-minimald-lifecycle-management-v1.md

📝 Walkthrough

Walkthrough

This PR implements full daemon lifecycle management for minimald: a pure state machine (lifecycle.rs), atomic state persistence with RAII rollback guard (state.rs), run --detach, status, and stop subcommands, and a new SSH-over-UDS client in minimal2 with Activate, Attach, Dash, and Destroy CLI subcommands plus Linux auto-spawn integration.

Changes

minimald Lifecycle Management

Layer / File(s) Summary
Lifecycle state machine and persistence layer
crates/minimald/src/lifecycle.rs, crates/minimald/src/state.rs
Defines Lifecycle/Action enums and next_state pure transition function with TransitionError; builds StateDir, State, and StartingGuard (RAII rollback guard) on top with atomic TOML writes via temp-file + fsync + rename and fd-lock advisory locking.
minimald run command (foreground and detach modes)
crates/minimald/src/cmd/run.rs, crates/minimald/Cargo.toml
Adds DEFAULT_DETACH_TIMEOUT_SECS, poll_uds_ready, run_detach (setsid + UDS poll), and run_foreground (multi-phase lifecycle transitions under write lock, UnixListener bind, Server::run, pid-file lifecycle).
minimald status and stop subcommands
crates/minimald/src/cmd/status.rs, crates/minimald/src/cmd/stop.rs
Adds StatusExit enum with run/run_with_state_dir (non-blocking lock check, human/JSON output, three exit codes) and stop run/run_with_state_dir (idempotent no-ops, SIGTERM + 5-second wait + SIGKILL escalation, state reset).
minimald CLI wiring
crates/minimald/src/main.rs, crates/minimald/src/lib.rs, crates/minimald/src/cmd/mod.rs
Adds --detach/--detach-timeout to ListenArgs, Status and Stop Command variants with early dispatch in async_main, delegates UDS startup to cmd::run::run, and exposes cmd/lifecycle/state as public modules.
minimal2 SSH/UDS client transport
crates/minimal2/src/client.rs, crates/minimal2/Cargo.toml
Introduces MinimalClientHandler, Client::connect with retry logic and none-auth, exec_in_session for SSH channel command execution, oneshot_rpc for JSON subsystem RPC, and resolve_socket_path with OS-specific defaults.
minimal2 CLI subcommands and Linux auto-spawn
crates/minimal2/src/main.rs, crates/minimal2/src/autospawn.rs
Adds Activate, Attach, Destroy, Dash CLI variants with cmd_activate/cmd_attach/cmd_dash/cmd_destroy implementations; rewrites Linux ensure_minvmd_running to read StateDir, poll Stopping, and spawn minimald run --detach.
Lifecycle management plan
plans/2026-06-16-minimald-lifecycle-management-v1.md
Adds the Markdown design plan for the lifecycle management implementation.

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
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 Hop, hop! The daemon wakes from sleep,
Its lifecycle state now faithfully kept,
A TOML file guards each start and stop,
With SIGTERM sent and RAII unwrapped,
The fzf fuzzy-finds sessions with glee,
While minimald run --detach runs free! 🌟


Note

🎁 Summarized by CodeRabbit Free

Your 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 @coderabbitai help to get the list of available commands and usage tips.

0chroma added a commit that referenced this pull request Jun 18, 2026
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.
0chroma added a commit that referenced this pull request Jun 19, 2026
…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.
@0chroma

0chroma commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

gonna close this since this was a real rough pass and it seems like we're gonna worry about more complex lifecycle stuff later

@0chroma 0chroma closed this Jun 19, 2026
@twitchyliquid64
twitchyliquid64 deleted the 0chroma/feat-minimald-lifecycle-management branch July 30, 2026 00:31
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