Skip to content

fix(cli): propagate handler exit code to process exit - #97

Merged
mrtaikandi merged 2 commits into
mainfrom
cli-fix
May 10, 2026
Merged

fix(cli): propagate handler exit code to process exit#97
mrtaikandi merged 2 commits into
mainfrom
cli-fix

Conversation

@mrtaikandi

@mrtaikandi mrtaikandi commented May 10, 2026

Copy link
Copy Markdown
Owner

Summary

The CLI was exiting 0 even when a handler returned a non-zero exit code (e.g., after the API responded with 400 HttpValidationProblemDetails, 409 ProblemDetails, or 422 ProblemDetails). Scripts and E2E tests silently passed when they should have failed; the symptom in the variable interpolation E2E suite was that Step08/Step09 collisions over the shared tier scope were masked until Step11 choked on non-JSON stdout.

Root cause was in src/GroundControl.Cli/Program.cs — top-level await builder.RunAsync(); discarded the int return value, so the program compiled as Task Main (void-returning) and the OS got exit code 0 regardless of what RunAsync produced. The System.CommandLine action plumbing (SetAction(ExecuteAsync), ParseResult.InvokeAsync, the chain through Command<,> and CliHost.RunAsync) was already returning the right int.

Changes

Top-level `await builder.RunAsync()` in Program.cs discarded the int the
CLI host returned, so the program compiled as `Task Main` and the OS
always saw exit code 0 — even when a handler returned non-zero after the
API responded with 400/409/422. Scripts and E2E tests silently passed
when they should have failed.

Surgical fix: return the int from RunAsync. Defensive fix in CliHost so
future consumers can't reintroduce the same mistake: every code path now
mirrors the exit code into Environment.ExitCode before returning, which
is honored even when the caller's Main returns void/Task.

Regression tests in CliExitCodeTests launch the real CLI binary and
assert the process exits non-zero for: missing required option, HTTP 400
HttpValidationProblemDetails, HTTP 409 ProblemDetails, HTTP 422
ProblemDetails. The test project gets a RepositoryRoot AssemblyMetadata
attribute so the tests can locate the CLI csproj — same pattern E2E uses.
CI builds Release while `dotnet run --no-build` defaults to Debug, so the
test was launching `dotnet run` against a missing Debug binary and failing
with "An error occurred trying to start process". Locally everyone's
already built Debug, so the test passed.

Fix: derive the CLI dll path from the test assembly's location (sibling
output directories under artifacts/bin/{Project}/{config}/) and invoke
`dotnet <CLI>.dll`. Config-agnostic and avoids the dotnet-run startup
overhead. Drops the now-unneeded RepositoryRoot AssemblyMetadata.
@mrtaikandi
mrtaikandi merged commit f06737f into main May 10, 2026
1 of 3 checks passed
@mrtaikandi
mrtaikandi deleted the cli-fix branch May 10, 2026 13:58
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