Repository navigation
Conversation
Flags after a child command now belong to the child: `hack run web bun --version` no longer prints Hack's version. Positionals marked `passthrough` (run, exec, host exec, env exec, session exec, x) end Hack's option parsing at the child's first word; a leading `--` stays optional and a later `--` reaches the child unchanged. Known native refusals print one closed line instead of a stack: the configuration refusal is E_NATIVE_PROJECT_UNSUPPORTED, the shared-source grant is E_PERMISSION, and the remaining value-free native start, run, exec, down and restart refusals are ClosedCliError. Usage refusals thrown by a handler print the message and a one-line help pointer instead of the full help. Native up --detach/--json/--target/services refuse by name with the supported foreground form. Authored native up checks runtime status before reserving its startup intent, so a stopped runtime refuses with E_DOCKER_UNAVAILABLE and leaves ps at not_started instead of pending. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A child `--json` no longer selects Hack's JSON error envelope: only argv before the passthrough child command and before the first `--` counts. A cancel or deadline during the authored start's runtime status check now refuses before the startup intent is reserved, so ps stays not_started. A start that fails because authored foreground control reports a stopped runtime wrapped in native_graph_foreground (#331) now reports E_DOCKER_UNAVAILABLE like the direct runtime_not_running code; the serve exit diagnostic carries the bounded cause code to the start error. Native project input and route refusals are E_NATIVE_PROJECT_UNSUPPORTED; branch, adaptation, AWS and fresh-exec environment refusals are E_CONFIG_INVALID; source admission and restart preflight refusals are closed. Each prints one line without a stack. `session exec` keeps its single quoted command: an unquoted command now refuses with a hint to quote it, and its help and docs say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roodboi
force-pushed
the
wf/g1-cli-refusals-b17
branch
from
October 19, 2026 18:12
75e4fe5 to
c5b4202
Compare
Complete the native refusal audit on the image-project up, restart, run, exec and ps paths. Configuration refusals carry a stable code (E_CONFIG_INVALID, E_NATIVE_PROJECT_UNSUPPORTED or E_PERMISSION). Integrity, ownership and change refusals are closed, so the CLI prints one line without a stack. Start-time receipt, ownership, dependency-plan and HTTPS verification failures no longer reuse the configuration refusal. Mark the dispatch run command as passthrough, so a child --version reaches the child. Give authored up/restart --detach the image project's one-line E_USAGE refusal. Add a CLI spawn test with a stub runtime for representative refusal sites, class and code assertions for the round-1 conversions, a non-running phase preflight case, and a failedAdmission wrapped-cause test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On macOS, authored up --detach now refuses with the image project's one-line E_USAGE message. The native plan CLI acceptance scripts expected E_NATIVE_PROJECT_UNSUPPORTED on every platform; expect E_USAGE on macOS and keep E_NATIVE_PROJECT_UNSUPPORTED elsewhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… refusals Three image-project `up` refusals still printed `ERROR: Error: ...` with a stack. They now print one closed line: - An image reference the runtime refuses (`registry_image`) or an authored service without an image is `E_NATIVE_PROJECT_UNSUPPORTED`. A malformed `runtime admit-image` reply is a closed integrity error, and a canceled admission no longer reports a reference refusal. - An invalid HACK_NATIVE_PREPARED_BASE / _STORE selection is `E_CONFIG_INVALID`. - An invalid workspace cache layout in HACK_NATIVE_ADAPTATION is `E_CONFIG_INVALID`, like its sibling adaptation refusals. Spawn tests drive each case through the real CLI with a stub runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This fixes four CLI parsing and refusal problems seen on the M3 with v5.0.0-next.5, plus the
up --detachmessage. The branch is rebased onto currentnext(54cc4c9), which includes #331.1. Child flags no longer reach Hack's parser
Before:
hack run web --profile toolchain bun --versionprinted Hack's version and exited 0.Now a positional can be marked
passthrough. From the child command's first word on, every token belongs to the child. That includes--version,--help,--json,--path,--branchand any other flag.--is still optional and is not passed on.--, as inhack run web npm test -- --watch, now reaches the child unchanged. Before, it was dropped.run,exec,host exec,env exec,session execandx.findPassthroughStartmirrors the parser, including options with default values.--jsonno longer selects Hack's JSON error envelope. Only argv before the child command and before the first--counts (hackOwnedArgv). Sohack run --profile toolchain web tool --jsonandhack exec web cat --jsonprint Hack's refusal as one human-readable line.hack --json run web tool --jsonstill prints the envelope.session exectakes its workspace command as one argument.hack session exec ws npm --versionnow refuses withUnexpected arguments: --version. <command> is one argument; quote the whole command, as in "npm --version".It does not print Hack's version.hack run [options] <service> [cmd...]plus a one-line note. Forsession execthe note says to quote the command.docs/reference/cli.mdis regenerated, anddocs/cli.mddescribes both rules.MCP
buildRunArgs/buildExecArgsalready put--before the service, so their behavior does not change.2. Native refusals print one closed line, not a stack
HackCliErrorwithE_NATIVE_PROJECT_UNSUPPORTED.HACK_NATIVE_SHARED_SOURCErequirement onupandrestartis nowE_PERMISSION, and the message says to set it and run again.E_CONFIG_INVALID.E_LIFECYCLE_FAILED.E_NATIVE_PROJECT_UNSUPPORTED. This covers a serviceenv_file, or a project.envwithout managed env. Unreviewed route labels are alsoE_NATIVE_PROJECT_UNSUPPORTED.E_CONFIG_INVALID.ClosedCliError: no stack, unlessHACK_DEBUG=1. This covers:NativePartialShutdownError,NativeHttpsStartupErrorandNativeHttpsLeaseAcquisitionErrorHACK_NATIVE_BINARY/HACK_NATIVE_HOMEselection3. A refused
runno longer prints the full helpWhen a handler throws a
CliUsageErrorafter parsing has succeeded, Hack now prints the message plus one line:See `hack run --help` for usage.Usage errors found while parsing still print the help. Under--jsonthe output is still oneE_USAGEenvelope.4. Authored
upwith the runtime stoppedruntime status --jsonrequest. A stopped runtime (phase notrunning, or a dead process) refuses at stagereservationwithruntime_not_running. Nothing is retained, sopsstaysnot_started. An inconclusive reply does not refuse.admission.reserve. A Ctrl-C during the check now endsnot-startedandpsstaysnot_started. Before this fix it endedretainedandpsshowedpending.E_DOCKER_UNAVAILABLE(aslogsalready does), with a message for each outcome and the next action. Before, it reportedE_STARTUP_INCOMPLETE. This now also covers fix(runtime): preserve authored startup and stopped-runtime causes #331's wrapped form: authored foreground control reportsnative_graph_foregroundwith causeruntime_not_running. The serve exit diagnostic now carries that bounded cause code toNativeAuthoredProjectStartError.nativeCauseCode.nativeCodesReportRuntimeNotRunningclassifies both forms, asisNativeRuntimeNotRunningdoes.pendingcan still be correct. Thepspending note anddocs/reference/native-authored-commands.mdexplain the remaining case. When the intent was reserved before the runtime stopped,pendingis still correct, because only the owner's cleanup proof may retire that intent.up --detachon nativeupwith--detach,--json,--targetor service names, andrestartwith--json,--targetor service names, now refuse with a one-lineE_USAGEmessage. (restarthas no--detachoption, sohack restart --detachis still an argument parse error that prints the help, as before.) It names the option and the supported form: runhack upin the foreground in its own terminal, usehack psandhack logsfrom another, and press Ctrl-C to stop it.up --detachkeepsE_NATIVE_PROJECT_UNSUPPORTEDand now adds the same guidance.Validation
At head c5b4202, based on
next54cc4c9:bun run --cwd packages/cli typecheck: clean.bun run --cwd packages/cli check: 0 errors and 34 warnings, the same count as onnext. Changed files lint clean with Biome.docs:cli-referenceis regenerated and is stable on a rerun.tests/integration-ownership.test.tsandtests/cli-spawn-env-isolation.test.tspass.integration-ownership,cli-spawn-env-isolation,cli-reference-docs,cli-help,cli-closed-error-output,cli-passthrough-argv,cli-command,native-runtime-not-running-cli,native-runtime-preflight,native-authored-command,native-project-observe,native-project-start,native-project-restart,native-runtime-client,native-exec-environment,native-project-input,native-project-routing,native-project-branch,native-project-adaptation,native-aws-environment,native-project-source-preflight,native-exec-stale-env,native-project-process,native-authored-project-process,native-authored-project-restart,native-authored-command-dispatch,mcp,native-project-branch-cliandnative-project-down.tests/native-authored-project-start.test.ts: 48/48 in one run. Under host load (load average 11–13), another run cascaded through the fixture-settlement guard. The first failing test passed on its own. This is the known flakiness under load, which also happens on unchangednext.New spawn tests use
tests/helpers/private-cli-env.ts, with a private HOME and an isolated Docker client config. No real Docker, HOME or keychain is used.Mutation proofs. Each fix was reverted, the listed tests failed, and the fix was restored.
parseCliArgvcli-passthrough-argvrunClireads--jsonfrom raw argv again--jsonspawn testsremaining()after the status check removedrun --profilerefusal testErrorErrorErrorup --detachrefusal removednative-project-observerefusal spawn test, and theup --detachclosed-line testnativeRuntimeStatusStoppedcheck removedE_DOCKER_UNAVAILABLEmapping casesnativeCode === runtime_not_runningnative_graph_foregroundmapping casenativeCauseCodenativeCodesReportRuntimeNotRunningremovedsession execquoting hint removedsession exectestcli-help"text help says where a child command's own argv begins"NativePartialShutdownErrorback toErrorErrorcli-reference-docsdrift testGaps
runtime statusthe way the Ruststatus()does for a stopped pool. Another fails serve withnative_graph_foreground/runtime_not_running. The status check is advisory: if the runtime stops between the check and serve, the retained-evidence path still applies, and the CLI reports it asE_DOCKER_UNAVAILABLEwith the pending explanation.tests/native-authored-project-recovery.test.tshas 2 failures ("new no-host death…" and "new no-host recovery retains original bindings…"). They fail the same way on unchangednexton this host.session execcommand is still a parse-time usage error, so it prints the root help after the quoting hint.dispatch run(Beta, unsupported) was left unchanged.native-authored-*internals, compose adoption, DNS) still throw plainError. This PR covers the image-project start, run, exec, down, restart (including its preflight) and dependency-refresh paths, their input/routing/branch/adaptation/AWS steps, and the authored start refusals.Rework 2
This round fixes the independent verifier's defects at c5b4202.
Refusal classes
Every known, user-reachable refusal on the image-project
up,restart,run,execandpspaths now prints one closed line. The class follows what was refused:HackCliErrorwith a stable code:E_CONFIG_INVALID: an unbounded--profile, a malformedHACK_NATIVE_DEPENDENCIESlistener selection or private selection file,HACK_NATIVE_ALLOW_HOSTS, a conflicting host-alias selection, an occupied HTTPS port, and a request over the runtime client's input or time budget (a large managed environment or a long startup timeout).E_NATIVE_PROJECT_UNSUPPORTED: a service that setsinitother thantruewith host dependencies, a Compose file outside the project, an active review with no supported service, and runtime metadata whose route labels do not match the reviewed plan.E_PERMISSION: an HTTPS port that needs host permission.ClosedCliError. Without--jsonit prints the message alone. Under--jsonthe code isE_UNEXPECTED, the existing code for a failure whose message is the only detail. This matches the down, exec, one-off and restart refusals from round 1. No existing code says "native state changed or could not be confirmed", andE_CONFIG_INVALIDwould wrongly tell an agent to fix its configuration. This covers:native-project-process.ts).Files converted beyond the verifier's list, because they are reachable from the same paths:
native-project-network.ts,native-project-host-aliases.ts,native-project-selection.ts,native-runtime-metadata.ts,native-project-finalization.ts,native-project-recovery.ts,native-project-cleanup-command.tsandnative-project-process.ts.The HTTPS port message no longer echoes the port number.
Defects
Native refusal audit. Fixed for every site the verifier listed, plus the files above. The repro
HACK_NATIVE_SHARED_SOURCE=1 hack up --profile <300 chars>now printsERROR: E_CONFIG_INVALID: Native profiles require at most 64 bounded nonempty names.with no stack, and makes no runtime request.tests/native-refusal-cli.test.tsspawns the source CLI withprivateCliEnvand a stub runtime. The stub answers scripted<word> <word>requests and otherwise reports a stopped runtime.ERROR:line, no stack frame, noError:and no help for these sites:native-project-run(profiles, unsafe mapping)native-runtime-client(unparseable reply)native-project-dependenciesnative-project-reviewnative-project-cachenative-project-start(dependency plan)native-project-retained-startupnative-project-restore(through a retainedupwith an acknowledged finalization)native-project-retained-images(throughrestart)native-project-networknative-project-https.tsornative-https-port.tswithout a live serve owner. Those refusals are raised in the foreground owner's readiness step, insideNativeHttpsStartupError, or in doctor checks that report a result instead.tests/cli-closed-error-output.test.tsrenders their real errors through the top-level handler instead: one line, no stack.hack dispatch runchild argv. Fixed. Thecommandpositional ispassthrough: true.hack dispatch run --project p bun --versionno longer prints Hack's version. Covered by the passthrough parse matrix (8 child-flag cases) and a source-CLI spawn case.docs/cli.mdlistsdispatch run.docs:cli-referenceis unchanged, because Beta commands are not in the reference.Mutations that survived.
{phase: "initializing", process_alive: null}, sophase === "stopped"now fails it.tests/helpers/closed-refusal.ts(expectClosedRefusalandexpectCodedRefusal, plus sync forms), asserts the class and code as well as the text. The restart-preflight (admission and the other closed restart refusals), down, exec, one-off, dependency-refresh and interrupted-cleanup tests now use it. A new test covers one-off argument validation.Integrity refusals in
native-project-start.ts. Fixed. The shared configurationrefused()is now used only for configuration. Each of these has its own closed message, and none saysE_NATIVE_PROJECT_UNSUPPORTEDor "cannot admit this configuration":ensure-imagereply that is not a content ID, and anauthoritative()receipt mismatch: "could not confirm the runtime image selection / the startup receipt";dependency_plan_id: "could not confirm the host dependency plan";retireRemovedMappingowner mismatch: "Native run mapping is owned by another run; it was not removed";Native HTTPS verification failed (NO_RESPONSE)or(REDIRECT_TARGET_REFUSED), in the same fixed form as the other verification failures, so the cleanup diagnostic keeps them.New tests in
tests/native-project-start.test.tscover each one.up --detach. Fixed. Both paths usenativeForegroundDetachRefusal. Authoredup --detachon macOS now refuses with the image project'sE_USAGEone-line message, which stands alone instead of trailing the generic sentence. Tests compare the authored refusal with the image refusal (code and full message) forup, and unit tests also passdetachto the authoredrestarthandler directly. The CLI cannot reach thatrestartcase:restarthas no--detachoption, sohack restart --detachis a parse error that prints the help (correction from verifier round 3).docs/reference/native-authored-commands.mdis updated.Also in scope: a new authored-start test covers the
NativeRuntimeRequestErrorwrapped-cause branch infailedAdmission. Theruntime statusrequest fails withnative_graph_foreground/runtime_not_running, and the start error keeps both codes at stagereservation.Remaining plain
Errorsites (internal invariants)native-runtime-client.ts: an argv shape that failsvalidExecResponseSelection/validBoundDrainSelection. Callers build these shapes, and no user input selects them. The user-reachable timeout and input bounds are now coded.native-project-process.ts: "Native receipt observation must be synchronous". This is a programming contract on the injected observer.native-project-exec.ts:137is closed but cannot be reached in practice. Dependency refresh refuses an absent mapping first with its own closed "has not been started" message, so mutating this site survives. It is a defensive check for a removal between refresh and inspection.native-authored-*internals andnative-https-owner*. Authored start failures are wrapped byfailedAdmissioninto coded CLI errors. HTTPS owner failures are wrapped byNativeHttpsStartupError, which is closed.Validation (rework 2)
Head f8e823e, based on c5b4202. f8e823e fixes the six native plan CLI acceptance scripts (
scripts/check-native-{env,host,routing,endpoint,process,acquisition}-plan-cli.ts), which run only in the macOS "Config compiler" CI job. On 6ec1ae2 that job failed: the scripts still expectedE_NATIVE_PROJECT_UNSUPPORTEDfor authoredup --detach. They now expectE_USAGEon macOS and keepE_NATIVE_PROJECT_UNSUPPORTEDelsewhere. Afterbun run build, all six,check-native-config-import-cliandcheck-config-compiler-clipass locally on Bun 1.4.2. All results come from Bun 1.4.2 (mise x bun@1.4.2 -- bun …) unless they say otherwise.bun run --cwd packages/cli typecheck: clean.bun run --cwd packages/cli check: 0 errors and 34 warnings, the same count asnext. Every changed file lints clean with Biome.docs:cli-referencewas rerun with no change. Betadispatchis not in the reference.next..HEAD): no leaks.bun run test(1.4.2): 74 failures in a 1h18m run on a host with load average 13–32.up --detachrefusal. It is now its own test that expects the sharedE_USAGEline on macOS.--timeout 60000and all passed, exceptnative-authored-project-recovery"new no-host death…", the known HACK-1282 failure onnext.--timeout 120000.cli-spawn-env-isolation,integration-ownership,cli-reference-docs,cli-help,native-runtime-not-running-cliandnative-project-observe.native-authored-project-startunder load.cli-spawn-env-isolationpassed.Mutation proofs. Each was applied to the source, the listed tests failed, and the source was restored. Every mutation failed at least one test except
exec.ts:137(explained above).Error(4 separate mutations)native-https-port.tscoded back toErrornative-project-dependencies.tscoded back toErrornative-project-run.tsmappingrefused()/ restart lock / profiles back toError(3 mutations)Errorin review, retained-images, retained-startup, restore, https and cache (6 mutations)ErrorError(4 mutations)Error(4 mutations)ErrorErrorErrorErrorErrorrefused()back toError;NativePartialShutdownError extends Errorrefused()(6 mutations)dispatch runpassthrough removedphase === "stopped"initializingcasefailedAdmissiondrops the wrapped causeVerifier round 3
Head ead8383, a fast-forward on 4d85f56. An independent verifier found three image-project
uprefusals that still printedERROR: Error: …and a stack.nextbehaves the same way, so these were sites the PR missed, not regressions. Each now prints one closed line.native-project-image-admission.ts). Three cases now print a closed line:registry_image), or a service with noimage, givesE_NATIVE_PROJECT_UNSUPPORTEDwith the existing message. The message does not include the reference.runtime admit-imagereply (for example{}) is now a closed integrity error: "Native runtime returned an invalid image admission response; …". Before, it reused the refusal text.native-prepared-base.ts). The three invalidHACK_NATIVE_PREPARED_BASE/_STOREselections are nowE_CONFIG_INVALID. They still refuse before any runtime request.native-workspace-cache.ts).refused()is nowE_CONFIG_INVALID, like its sibling adaptation refusals.restart --detach. This body claimedrestart --detachrefuses withE_USAGE.restarthas no--detachoption, so it is a parse error that prints the help, and that is unchanged. Theup --detachsections above are corrected.The new spawn cases are in
tests/native-refusal-cli.test.ts. The stub runtime can now also fail a request with a closed stderr code. Each case checks for exactly oneERROR:line, no stack, noError:, no help, and the runtime calls made:ghcr.io/example/app:1reference (the output does not containghcr.io);{}admission reply;HACK_NATIVE_PREPARED_BASE=bogus, with no runtime request;{"version":1,"workspaceCache":{"bogus":1}}, which stops afterruntime check-project-share.tests/native-prepared-base.test.tsnow asserts the code withexpectCodedThrow.Mutation proofs (Bun 1.4.2). In each case the source was changed as described, the spawn file was rerun, and the source was then restored:
native-project-image-admission.tsreverted to 4d85f56ERROR: Error: …malformed()back to a plainErrornative-prepared-base.tsreverted to 4d85f56native-workspace-cache.tsreverted to 4d85f56Gates on ead8383 (Bun 1.4.2):
bun run --cwd packages/cli typecheckis clean.checkreports 0 errors and the same 34 warnings. The changed files are formatted with Biome.native-project-*andnative-runtime-*test file, plusnative-prepared-base,native-workspace-cache,native-refusal-cli(16/16),native-selection-refusals,cli-spawn-env-isolation,cli-helpandcli-reference-docs.bun run buildandbun run build:config-compiler, all sixcheck-native-*-plan-cliscripts,check-native-config-import-cliandcheck-config-compiler-cliexit 0..gitleaks.tomland--redactfound no leaks. The agent sandbox blocks thegitleaks gitsubcommand, so the scan usedgitleaks dirovergit log -p --unified=0 origin/next..HEAD(6 commits, about 238 KB).bun run testwas not rerun this round.🤖 Generated with Claude Code