Skip to content

feat(minvmd): add run, status, and stop subcommands - #351

Merged
norrietaylor merged 4 commits into
mainfrom
sdd/330-run-status-stop-subcommands-20981da5b3639a88
Jun 5, 2026
Merged

feat(minvmd): add run, status, and stop subcommands#351
norrietaylor merged 4 commits into
mainfrom
sdd/330-run-status-stop-subcommands-20981da5b3639a88

Conversation

@gominimal-aw-bot

@gominimal-aw-bot gominimal-aw-bot Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Implements R4.2, R4.3, and R4.4 from the minvmd host daemon spec.

Closes #330

Changes

minvmd run (R4.2)

  • Foreground supervisor: manages lifecycle (Stopped → Starting → Running → Stopped) via brief write locks on lifecycle.lock
  • StartingGuard RAII rollback resets to Stopped if boot fails before the VMM child signals READY
  • --detach: spawns a detached supervisor (setsid) and polls the host UDS until accepting connections; configurable --timeout (default 8 s)
  • Macros-only on macOS; Linux build stubs bail immediately with a clear error

minvmd status (R4.3)

  • Reads state.toml; prints human-readable (default) or JSON (--json) output
  • JSON fields: state, vmm_pid, uptime_seconds, vcpus, ram_mib
  • Exit 0 = Running, 1 = stopped/other, 2 = lock contention (detected via non-blocking try_read() on lifecycle.lock)

minvmd stop (R4.4)

  • Reads vmm_pid from state under write lock, then releases lock before signaling
  • Sends SIGTERM; polls process existence every 100 ms for up to 5 s; escalates to SIGKILL on timeout
  • Removes vmm.pid and writes Stopped state under lock
  • Idempotent: Stopped, NotProvisioned, and Stopping states all return Ok(()) immediately

Proof artifacts

Test — unit tests for poll_uds_ready, StatusExit exit codes, stop idempotency and cleanup:

cargo test -p minvmd

Tests that must pass (and fail before this PR):

  • cmd::run::tests::poll_uds_returns_ok_when_listener_is_ready
  • cmd::run::tests::poll_uds_times_out_when_no_listener
  • cmd::run::tests::run_bails_on_non_macos (Linux CI only)
  • cmd::status::tests::not_provisioned_exits_stopped
  • cmd::status::tests::stopped_state_exits_stopped
  • cmd::status::tests::running_state_exits_running
  • cmd::status::tests::starting_state_exits_stopped
  • cmd::status::tests::json_output_contains_required_fields
  • cmd::status::tests::lock_contention_exits_2
  • cmd::stop::tests::stop_is_noop_when_not_provisioned
  • cmd::stop::tests::stop_is_noop_when_already_stopped
  • cmd::stop::tests::stop_is_noop_when_already_stopping
  • cmd::stop::tests::stop_cleans_up_running_state_with_nonexistent_pid
  • cmd::stop::tests::stop_with_no_pid_in_state_still_resets_to_stopped

CLI — stop is idempotent and exits 1 on a stopped daemon:

minvmd stop && minvmd status --json  # exits 1, prints {"state":"stopped",...}

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • index.crates.io

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "index.crates.io"

See Network Configuration for more information.

Generated by sdd-execute (sonnet tier) for issue #330 · ● 48.5M ·

Summary by CodeRabbit

  • New Features

    • Added minvmd run to start the VM supervisor in foreground or detached background with a configurable timeout (and a default).
    • Added minvmd status to report daemon state and uptime (human or --json) and return distinct exit codes for running/stopped/lock contention.
    • Added minvmd stop to gracefully stop the VM supervisor, signal the VMM, and clean up persisted state.
  • Tests

    • Added unit tests covering run/status/stop behaviors, timeouts, and lock contention.

Implements R4.2, R4.3, and R4.4 from the minvmd host daemon spec:

- `minvmd run`: foreground lifecycle supervisor with StartingGuard RAII
  rollback; `--detach` spawns background supervisor and polls host UDS
  until accepting connections (configurable timeout, default 8s).
- `minvmd status`: reads state.toml, prints human-readable or JSON output
  (fields: state, vmm_pid, uptime_seconds, vcpus, ram_mib); exit 0 if
  running, 1 if stopped, 2 on lifecycle lock contention.
- `minvmd stop`: sends SIGTERM to vmm child, waits 5s, escalates to
  SIGKILL; removes vmm.pid and resets state.toml to Stopped; idempotent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6eff42e5-1021-4ac4-a8c6-ebdfb3e952d4

📥 Commits

Reviewing files that changed from the base of the PR and between bfaa3c7 and 459a12d.

📒 Files selected for processing (1)
  • crates/minvmd/src/cmd/run.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/minvmd/src/cmd/run.rs

📝 Walkthrough

Walkthrough

Adds three minvmd CLI subcommands: run (foreground supervisor and detached startup with UDS readiness polling), status (state query with lock-contention detection and optional JSON), and stop (graceful shutdown with SIGTERM/SIGKILL escalation). Also wires CLI dispatch and adds a workspace serde_json dependency.

Changes

VM Lifecycle Management

Layer / File(s) Summary
Dependencies and module exports
crates/minvmd/Cargo.toml, crates/minvmd/src/cmd/mod.rs, crates/minvmd/src/main.rs
Adds serde_json as a workspace dependency, exposes new cmd submodules run, status, stop, and adds Run/Status/Stop variants.
Run supervisor (detach & foreground)
crates/minvmd/src/cmd/run.rs
Implements minvmd run API (run, poll_uds_ready, DEFAULT_DETACH_TIMEOUT_SECS). Detached mode re-execs with setsid and polls host UDS until connectable or timeout; foreground mode performs lifecycle locking, spawns __krun-vmm, awaits READY marker (5s), transitions Starting→Running→Stopped, cleans up on failure, and includes unit tests.
Status query command
crates/minvmd/src/cmd/status.rs
Implements minvmd status with StatusExit (Running/Stopped/LockContention), non-blocking lifecycle read lock, optional JSON output containing state, vmm_pid, and uptime_seconds, and human-readable output. Includes tests for state mapping, JSON path, and lock contention.
Stop command and signal handling
crates/minvmd/src/cmd/stop.rs
Implements minvmd stop entrypoint: acquires write lock, idempotent early returns for terminal states/Stopping, optionally signals recorded vmm_pid (SIGTERM, poll up to 5s, escalate to SIGKILL), removes vmm.pid, writes Stopped to state, and includes tests for idempotency, ESRCH handling, and PID validation.
CLI command wiring in main
crates/minvmd/src/main.rs
Adds Run (--detach, --timeout), Status (--json), and Stop variants and dispatches to minvmd::cmd::{run,status,stop}::run, using Status’s returned exit code when non-zero.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as minvmd_cli
  participant Supervisor as minvmd_run_parent
  participant Child as minvmd_run_child
  participant VMM as __krun-vmm
  participant HostUDS as Host_UDS

  User->>CLI: minvmd run --detach
  CLI->>Supervisor: spawn re-exec (setsid pre_exec)
  Supervisor->>Child: detach and supervise
  Child->>VMM: spawn __krun-vmm with READY socket env
  VMM->>HostUDS: emit READY marker
  Supervisor->>HostUDS: poll_uds_ready -> connect
  Supervisor-->>User: return success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • gominimal/minimal#332: Introduced lifecycle/state primitives (StateDir, state.toml, lifecycle.lock) consumed by these subcommands.
  • gominimal/minimal#345: Added host UDS bridge helpers and permission checks used by the new run flow.

Suggested labels

needs-human

Suggested reviewers

  • norrietaylor
  • evanspearman

Poem

🐰 I hopped in code to add three charms,

Run detaches with setsid's arms,
Status peeks while locks hold tight,
Stop sends term then kill by night,
A rabbit cheers the lifecycle's calm.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding three new subcommands (run, status, stop) to minvmd.
Linked Issues check ✅ Passed All code requirements from #330 are met: run subcommand (R4.2) with detach and timeout, status subcommand (R4.3) with JSON output and exit codes, stop subcommand (R4.4) with signal handling, plus required unit tests and CLI artifact validation.
Out of Scope Changes check ✅ Passed All changes directly implement the three subcommands and related infrastructure (Cargo.toml serde_json dependency, cmd/mod.rs module declarations, main.rs CLI integration) as specified in issue #330 scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/minvmd/src/cmd/status.rs`:
- Around line 51-53: The local variable rw declared as "let mut rw =
state_dir.lifecycle_lock()..." is never mutated; change it to immutable by
removing "mut" (use "let rw = state_dir.lifecycle_lock()...") so the subsequent
call rw.try_read() compiles without an unnecessary mutable binding; update the
declaration near the status logic that calls lifecycle_lock() and try_read().

In `@crates/minvmd/src/cmd/stop.rs`:
- Around line 71-77: The vmm_pid value must be range-checked before casting in
signal_and_wait so it cannot wrap into a negative PID; update signal_and_wait
(where you do let pid_t = pid as libc::pid_t) to perform a checked conversion
from u32 to libc::pid_t (e.g., using TryInto or comparing against
libc::pid_t::MAX) and return an Err (or a clear error) when vmm_pid is None or
exceeds the pid_t max, rather than blindly casting; ensure callers that read
state.vmm_pid (from state.rs) handle this error path so you never call
libc::kill with a wrapped/negative pid.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 958c7a66-409d-4989-ad7f-4a2591f68e56

📥 Commits

Reviewing files that changed from the base of the PR and between c332b4c and 12a6f3e.

📒 Files selected for processing (6)
  • crates/minvmd/Cargo.toml
  • crates/minvmd/src/cmd/mod.rs
  • crates/minvmd/src/cmd/run.rs
  • crates/minvmd/src/cmd/status.rs
  • crates/minvmd/src/cmd/stop.rs
  • crates/minvmd/src/main.rs

Comment thread crates/minvmd/src/cmd/status.rs Outdated
Comment thread crates/minvmd/src/cmd/stop.rs Outdated
@gominimal-aw-bot

This comment has been minimized.

@norrietaylor

Copy link
Copy Markdown
Member

/revise fix the ci failure

Validate the coderabiit issue and fix them if they are valid

@gominimal-aw-bot gominimal-aw-bot Bot mentioned this pull request Jun 5, 2026

@gominimal-aw-bot gominimal-aw-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by sdd-review for issue #351 · ● 41.2M

Comment thread crates/minvmd/src/cmd/stop.rs
- status.rs: remove mut from let mut rw (Clippy unused-mut)
- stop.rs: replace pid as libc::pid_t with checked try_from to
  prevent silent wrapping when vmm_pid > i32::MAX; reject pid <= 0
  to avoid signaling the wrong process group
- Add tests for both new error paths

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gominimal-aw-bot

Copy link
Copy Markdown
Contributor Author

Commit pushed: cde89fb

Generated by sdd-execute (sonnet tier) · ● 11.4M

@gominimal-aw-bot

This comment has been minimized.

@gominimal-aw-bot gominimal-aw-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by sdd-review for issue #351 · ● 34.9M

Comment thread crates/minvmd/src/cmd/run.rs
The macOS-gated run_detach and run_foreground functions call .context()
and .with_context() on Result values, which require anyhow::Context to
be in scope. The trait was missing, which would cause a compile error on
macOS (not caught on Linux CI because the callers are cfg-gated).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gominimal-aw-bot

Copy link
Copy Markdown
Contributor Author

Commit pushed: bfaa3c7

Generated by sdd-execute (sonnet tier) · ● 10.1M

@gominimal-aw-bot

This comment has been minimized.

@gominimal-aw-bot gominimal-aw-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by sdd-review for issue #351 · ● 29.9M

Comment thread crates/minvmd/src/cmd/stop.rs
Comment thread crates/minvmd/src/cmd/run.rs
The `.context()`/`.with_context()` calls in run.rs live entirely in
the macOS-gated supervisor functions; the Linux build is a no-op stub.
The unconditional `use anyhow::Context as _;` was therefore unused on
Linux, failing `cargo clippy --all-targets -- -D warnings`
(unused-imports) in the Linux CI job while compiling cleanly on macOS.

Scope the trait import to `#[cfg(target_os = "macos")]` so it is
present exactly where it is used. `Result` and `bail` stay
unconditional — both are used by the Linux stub and poll_uds_ready.

Refs: #330
@gominimal-aw-bot

Copy link
Copy Markdown
Contributor Author

sdd-validate · Implementation boundary · PR #351

Boundary: Implementation — all changed files are Rust source, build configuration, or lockfile; no spec, architecture, or decisions files.

Task: #330 (Run, status, and stop subcommands) · requirements R4.2, R4.3, R4.4


Findings

1. Proof artifacts re-executed and passing

  • Infocargo test -p minvmd (Test proof): cannot execute in this environment (no Rust toolchain; index.crates.io firewalled). Deferred to consumer CI: the CI / build job (.github/workflows/ci.yml) runs cargo test --verbose, a superset that exercises every test in the minvmd crate. The PR's mergeable_state: blocked is consistent with required status checks gating merge on this job.

  • Infominvmd stop && minvmd status --json (CLI proof): cannot execute (no built binary). Deferred to consumer CI: the semantics this CLI demo validates — stop idempotency (R4.4) and status exit codes/JSON fields (R4.3) — are covered by the unit tests in the test proof artifact above (stop_is_noop_when_*, *_exits_stopped, *_exits_running, json_output_contains_required_fields, lock_contention_exits_2), which cargo test --verbose in CI executes.

2. Changed files within task scope

  • WarningCargo.lock and crates/minvmd/Cargo.toml are changed but not listed in the task's files in scope block. Both changes add the serde_json dependency required by status.rs for --json output. Neither is a protected path (.github/, decisions/, templates/.github/, secrets). These are ancillary build-configuration changes necessarily implied by the in-scope implementation.

  • Pass — all other changed files (crates/minvmd/src/cmd/run.rs, crates/minvmd/src/cmd/status.rs, crates/minvmd/src/cmd/stop.rs, crates/minvmd/src/cmd/mod.rs, crates/minvmd/src/main.rs) are within the task's files in scope block.

3. No real credentials in the diff

  • Pass — no secrets, tokens, keys, or credentials detected in the diff.

Result: Implementation boundary passed clean (no Blocker findings). 1 Warning, 2 Info.

Lifecycle: Feature #311 already carries sdd:review; no lifecycle move needed (idempotent — already advanced by an earlier task's clean pass).

Generated by sdd-validate for issue #351 · ● 20.7M ·

@gominimal-aw-bot gominimal-aw-bot Bot added the needs-human An agent handed off; a human must act, then clear this label. label Jun 5, 2026

@gominimal-aw-bot gominimal-aw-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by sdd-review for issue #351 · ● 26M

Comment thread crates/minvmd/src/cmd/run.rs
Comment thread crates/minvmd/src/cmd/stop.rs
@norrietaylor
norrietaylor enabled auto-merge (squash) June 5, 2026 05:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human An agent handed off; a human must act, then clear this label.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Run, status, and stop subcommands

1 participant