Skip to content

feat(cli): wait for ssh on up; tell booting from failed - #157

Merged
NovusEdge merged 1 commit into
mainfrom
qol/readiness
Sep 29, 2026
Merged

NovusEdge merged 1 commit into
mainfrom
qol/readiness

Conversation

@NovusEdge

Copy link
Copy Markdown
Owner

What changed

  • stoat up blocks until sshd answers, then until applied recipes' health checks pass, when stdout is not a terminal, under --json, or with --wait. --no-wait opts out. --no-apply skips the health part.
  • up on a running VM and down on a stopped VM print "already running" / "already stopped" and exit 0. up --wait on a running VM still waits. rm on a missing VM still exits 1.
  • stoat wait redraws an elapsed-time line on stderr when stderr is a terminal (not under --json or -q).
  • sshx.Run returns sshx.ErrUnreachable when ssh exits 255 and its stderr is a connection failure (refused, banner exchange timeout, no route, reset, closed). A command that exits 255 on its own stays a result. The error maps to the existing cannot_reach code. All guest tools that go through sshx.Run get this.
  • CLI exec, cp and ssh say <vm> is booting; run stoat wait <vm>. cp uses the same stderr check on scp output. ssh probes for a banner for up to 3s before it replaces the process.
  • MCP tool errors for ErrUnreachable end with call wait first.
  • MCP wait defaults timeout_seconds to 300 (cap stays 600), stated in the schema and docs.
  • MCP exec returns partial stdout/stderr with timed_out: true and a message pointing at exec_bg instead of a timeout error. The command now runs under the guest's timeout -s KILL <n> when the guest has timeout, because killing the local ssh alone leaves the remote process running.
  • wire.CommandResult gains optional timed_out and message fields.
  • Docs: docs/reference/cli.md, docs/reference/mcp.md, docs/troubleshooting.md.

Existing tests that pinned down on a stopped VM as an error now expect success. TestFanOutStopsAtTheFirstError uses rm for its failing step, since down no longer fails there.

Needs a live check

No VM was booted for this PR; the tests use fake ssh and fake running VMs. Please verify on a real guest (label needs-live-boot):

  • stoat up x | cat blocks until ssh works and an immediate stoat exec x true succeeds.
  • stoat up --no-wait x && stoat exec x true prints the booting hint and exits 1.
  • The stderr markers in sshx.transportMarkers match what the installed OpenSSH prints for a real booting guest.
  • timeout -s KILL exists and ends the command on alpine (busybox) and a cloud image. Busybox timeout may not kill grandchildren of a shell.
  • stoat ssh x on a booting VM prints the hint; on a ready VM it opens the session with no visible delay.
  • stoat wait x draws the progress line on a real terminal and clears it.

Checks

go test ./..., golangci-lint run ./... (0 issues), gofmt and go vet pass. shellcheck is not installed here and no shell files changed. go test -race on sshx, mcpsrv and cli passes; core's TestStartAndStopPropagateTheProvidersError failed under race only because the host had 476 MB free and the memory guard refused the fake start. It passes without -race.

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 16 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 20fe17b1-243d-46d5-ad49-291fd00edf87

📥 Commits

Reviewing files that changed from the base of the PR and between 40d8695 and c89cbd8.

📒 Files selected for processing (23)
  • docs/reference/cli.md
  • docs/reference/mcp.md
  • docs/troubleshooting.md
  • internal/cli/cli.go
  • internal/cli/cli_test.go
  • internal/cli/grammar.go
  • internal/cli/json_test.go
  • internal/cli/run_access.go
  • internal/cli/run_project_test.go
  • internal/cli/run_vm.go
  • internal/cli/run_vm_idempotent_test.go
  • internal/cli/run_wait.go
  • internal/cli/wire/dto.go
  • internal/cli/wire/errors.go
  • internal/core/copy.go
  • internal/core/exec.go
  • internal/mcpsrv/server.go
  • internal/mcpsrv/tools_exec.go
  • internal/mcpsrv/tools_exec_test.go
  • internal/mcpsrv/tools_vm.go
  • internal/mcpsrv/tools_vm_test.go
  • internal/sshx/run.go
  • internal/sshx/run_test.go

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

up now blocks until sshd answers and recipe health checks pass when stdout is not a terminal or under --json, or with --wait; --no-wait opts out. up on a running VM and down on a stopped VM exit 0. wait draws elapsed time on a terminal's stderr.

sshx.Run returns ErrUnreachable when ssh exits 255 with its own connection error, so a booting VM is no longer reported as a command that exited 255. The CLI says the VM is booting and to run stoat wait; MCP returns cannot_reach with call wait first. cp maps scp's connection errors the same way.

MCP wait defaults to 300s. MCP exec returns partial output with timed_out=true on timeout and runs the command under the guest's timeout so the remote process ends too.

Signed-off-by: NovusEdge <novusedge0@gmail.com>
@NovusEdge
NovusEdge merged commit fd5b6e7 into main Sep 29, 2026
8 of 9 checks passed
@NovusEdge
NovusEdge deleted the qol/readiness branch September 29, 2026 18:33
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