Skip to content

fix(native): report a stopped runtime from ps and print closed errors without a stack - #325

Merged
roodboi merged 2 commits into
nextfrom
wf/native-ps-not-running-b15
Oct 14, 2026
Merged

roodboi merged 2 commits into
nextfrom
wf/native-ps-not-running-b15

Conversation

@roodboi

@roodboi roodboi commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Problem

On the M3, with a native candidate built from next and the native runtime stopped, hack-v5 ps --path <project> --branch alpha exited 1. It printed the closed native refusal Native runtime request failed (runtime_not_running); inspect owned state before retrying., then printed it again as ERROR: Error: … followed by a raw JavaScript stack trace. A stopped runtime is an ordinary state for a read-only status command.

Cause: nativeProjectPs let the graph inspect refusal propagate. NativeRuntimeRequestError was a plain Error, so the top-level handler took the "unexpected exception" path, which calls cancel(message) and then prints error.stack.

Change

ps (image-native and authored)

  • With a saved mapping or Ready generation and native code runtime_not_running, ps exits 0. It reports status runtime_not_running with the saved run, a null phase and no items. Text output prints Native runtime is not running; no services are running. and then the empty table. Every other native refusal still fails.
  • Image-native ps --json now prints the standard {ok: true, data} envelope that authored native ps already used. data is {backend: "native", status, run, phase, items}. This changes the shape of image-native ps --json: it used to print the bare object. scripts/accept-native-frontend.py and its Python test fake now unwrap the envelope. The MCP status tool passes the JSON through unchanged, and crash capture reads only the Docker-path composeProject, so neither needs a change.
  • Image-native human ps also prints Native project has not been started. for not_started, matching authored ps.

logs --no-follow (image-native and authored)

  • A stopped runtime now refuses with E_DOCKER_UNAVAILABLE: Native runtime is not running; no service logs are available. Start the native runtime and retry. and exit 1. Before, image-native logs printed a stack, and authored logs printed the misleading "owner/selection changed" message.

Top-level error handler (src/cli/run.ts)

  • New ClosedCliError (in src/lib/cli-result.ts) marks errors whose message is a complete, value-free sentence. NativeRuntimeRequestError and the closed refusals in native-project-observe now extend it.
  • ClosedCliError and HackCliError print their message only. The stack is added only with HACK_DEBUG=1. Unexpected exceptions still always print their stack, as before. --json output is unchanged: one error envelope.

Other read-only commands checked

  • open --json (native): reads only the saved routing generation and never calls the runtime, so a stopped runtime does not affect it.
  • doctor (native): turns runtime failures into warn checks and does not throw, so no change was needed.
  • hack status (projects --details) and global status: no native runtime path.
  • Native Compose ps: observes through the engine, not invokeNativeRuntime, so this change does not cover it.

Docs: docs/guides/native-candidate.md, docs/reference/native-authored-commands.md and docs/cli.md (HACK_DEBUG).

Validation

  • New tests/native-runtime-not-running-cli.test.ts spawns the source CLI with privateCliEnv and a fake native binary that answers {"code":"runtime_not_running"} (or another code) on stderr with exit 1. It covers: human ps (exit 0, note, table, exactly one graph inspect call, no stack frame); ps --json (exact ok envelope); logs web --no-follow, human and --json (exit 1, E_DOCKER_UNAVAILABLE, no stack); a different native code (ps exit 1, message printed once, no stack, --json error envelope); and HACK_DEBUG=1 (stack present).
  • tests/native-project-observe.test.ts: unit tests for the ps status and logs refusal with injected invoke. They also check that other codes and an unstructured Error("runtime_not_running") still throw. The existing not-started CLI test now expects the envelope.
  • tests/native-authored-project-observe.test.ts: unit test for the authored status. Another native code still refuses, and cancellation still wins. Two macOS CLI cases (human and --json) use privateCliEnv.
  • tests/cli-closed-error-output.test.ts: in-process handler output for closed, coded and unexpected errors, HACK_DEBUG, and --json.
  • Mutations, each killed by the new tests: restore the ps throw (3 failures); restore the authored throw (3); drop the logs mapping (2); remove the handler's closed branch (2); make NativeRuntimeRequestError extend Error (2); revert ps JSON to the bare object (2); ignore HACK_DEBUG (2). With a bare ps JSON reply, the Python acceptance fake fails 14 of 24 tests.
  • Passing: focused native and CLI files (280 tests across 15 files), plus 244 tests across 11 related native files. Both include tests/integration-ownership.test.ts and tests/cli-spawn-env-isolation.test.ts. Also passing: python3 -m unittest … test_native_frontend_acceptance.py (24), bun run --cwd packages/cli typecheck (0 errors) and bun run --cwd packages/cli check (0 errors; the 34 warnings are in files this PR does not touch). Changed test files pass ultracite check.

Follow-up 0b357fa adds tests for the authored logs --no-follow stopped-runtime path (E_DOCKER_UNAVAILABLE, exit 1, no stack) and the authored ps drift recheck. It also fixes a bug the new logs test found: the held saved-read owner turned runtime_not_running into a generic E_LIFECYCLE_FAILED.

Gaps

  • Not yet run against a real stopped native runtime on the M3. The evidence uses the native binary's closed stderr contract ({"code":"runtime_not_running"}, exit 1), which packages/runtime-core emits from ObservedGuest::connect and OwnedGuest::connect_mode.
  • The full bun run test suite and hosted CI have not run yet.
  • Under --json, a closed native refusal still reports E_UNEXPECTED; this PR does not change its error code.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

roodboi and others added 2 commits October 14, 2026 00:08
… without a stack

A stopped candidate runtime made `hack ps` exit 1 with the native refusal
printed twice and a raw JavaScript stack trace. A stopped runtime is an
ordinary state for a read-only status command.

- Image-native and authored `ps` treat `runtime_not_running` as a status:
  exit 0, a one-line "Native runtime is not running; no services are
  running." note plus the empty table, and status `runtime_not_running`
  (saved run, null phase, no items). Other native refusals still fail.
- Image-native `ps --json` now prints the standard `{ok: true, data}`
  envelope, matching authored native ps. The acceptance driver unwraps it.
- Native `logs --no-follow` (image and authored) refuse a stopped runtime
  with `E_DOCKER_UNAVAILABLE` and a closed message.
- NativeRuntimeRequestError and native observe refusals are ClosedCliError:
  the top-level handler prints their message once, with the stack only
  under HACK_DEBUG=1. HackCliError also shows its stack under HACK_DEBUG=1.
  Unexpected exceptions keep printing their stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… generic refusal

The held saved-read owner turns every error raised inside it into its own
artifact refusal, so authored `logs --no-follow` against a stopped runtime
printed E_LIFECYCLE_FAILED and the E_DOCKER_UNAVAILABLE mapping in
runAuthoredLogs was unreachable. The read now keeps a runtime_not_running
reply as a value, rechecks the saved selection and executable, and rethrows
it after the owner closes.

Tests: source CLI authored logs against a stopped fake runtime (human and
--json: E_DOCKER_UNAVAILABLE, exit 1, one line, no stack); a stopped reply
after executable drift refuses; authored ps refuses saved-selection or
executable drift that precedes a stopped-runtime reply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@blacksmith-sh

This comment has been minimized.

@roodboi
roodboi marked this pull request as ready for review October 14, 2026 05:44
@roodboi
roodboi merged commit e270625 into next Oct 14, 2026
32 of 33 checks passed
@roodboi
roodboi deleted the wf/native-ps-not-running-b15 branch October 14, 2026 05:44
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