Rust CLI: complete usage-error exit-code coverage (exit 2, matching Node) - #332
Merged
Conversation
The port's CliError usage mechanism (exit 2 for invalid invocations, matching the Node CLI's oclif convention) covered the generated-command path and a few friendly families but left ~100 parse/validation sites returning plain anyhow errors that exited 1: unknown flags, unknown boolean/short flags, missing flag values, unexpected arguments, and unknown subcommands across agent, auth, domains, doctor, inbox, memories, org-secrets, payloads, search, emails, mail, wake, routes, functions, and payments; plus auth post-parse invocation-shape checks (missing or extra positionals, --signup-code without an email, missing --accept-terms, conflicting or absent verification-code sources, --code-from-stdin on a TTY) and the unknown functions template. All now route through crate::usage_error. main_entry now walks the full error chain to find CliError instead of downcasting only the outermost error, so a usage error wrapped by context() still exits 2. Added a regression test. State-dependent failures stay at exit 1, matching the Node CLI per the parity fixtures: describe unknown operation, config value validation, doctor failures, pending-auth mismatches, unreadable --code-from-file, unset --code-from-env, browser login denial. payments integration test updated: `payments charge --bogus` now exits 2 like its missing-value sibling assertion and the Node CLI. Validation: make rust-cli-check, cli-parity (453 cases green). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Confidence Score: 5/5This looks safe to merge.
|
| Filename | Overview |
|---|---|
| cli-rust/src/auth_commands.rs | Updates auth parsing and verification-code paths so usage errors keep the expected exit code. |
| cli-rust/src/lib.rs | Walks the error chain when choosing the process exit code for CLI usage errors. |
| cli-rust/src/doctor_commands.rs | Converts doctor argument errors to usage errors and removes the unused macro import. |
| test-fixtures/cli-parity/cases.json | Updates parity expectations for usage-error exit code behavior. |
Reviews (8): Last reviewed commit: "Merge rust-cli-port (5 fixes already lan..." | Re-trigger Greptile
Greptile: the --code-from-stdin TTY usage error was flattened by map_err(anyhow!) before reaching exit-code selection, so it exited 1. The closure now re-marks usage errors after adding the flag prefix while leaving genuine read failures at exit 1. Also convert the sibling body-flag validation sites the same sweep missed: the --raw-body/--body-file conflict and invalid-JSON values for both flags (file-read failures stay runtime). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The parity fixtures pin invalid --raw-body JSON to exit 1 on both CLIs (the Node CLI treats body construction as runtime, not parse), so the --raw-body/--body-file conflict and invalid-JSON conversions from the previous commit contradicted Node. Reverted; the --code-from-stdin marker preservation stands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
etbyrd
force-pushed
the
rust-cli-port
branch
2 times, most recently
from
July 21, 2026 00:53
64ae18d to
8303e82
Compare
The base snapshot was force-rebased again (f1b5005 -> 411c26c), rewriting auth_commands.rs. Took the new upstream auth code and reapplied the usage conversions on top (24 parse/validation sites plus the --code-from-stdin marker preservation); upstream's new auth error sites are state/runtime failures and correctly stay at exit 1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
etbyrd
force-pushed
the
rust-cli-port
branch
5 times, most recently
from
July 21, 2026 17:25
322c5b5 to
0765fa5
Compare
# Conflicts: # .github/workflows/rust-cli-release.yml # .github/workflows/sdk-checks.yml # Makefile # cli-rust/src/agent_commands.rs # cli-rust/src/auth_commands.rs # cli-rust/src/completion_commands.rs # cli-rust/src/config.rs # cli-rust/src/doctor_commands.rs # cli-rust/src/domains_commands.rs # cli-rust/src/emails_commands.rs # cli-rust/src/friendly.rs # cli-rust/src/functions_commands.rs # cli-rust/src/help_snapshots.generated.rs # cli-rust/src/inbox_commands.rs # cli-rust/src/lib.rs # cli-rust/src/mail_commands.rs # cli-rust/src/memories_commands.rs # cli-rust/src/org_secrets.rs # cli-rust/src/payloads.rs # cli-rust/src/payments.rs # cli-rust/src/routes_commands.rs # cli-rust/src/search_commands.rs # cli-rust/src/wake_commands.rs # cli-rust/tests/completion_commands.rs # cli-rust/tests/config.rs # cli-rust/tests/payloads.rs # cli-rust/tests/payments.rs # scripts/generate-rust-cli-help-snapshots.mjs # scripts/run-cli-help-sweep.mjs # scripts/run-cli-parity.mjs # test-fixtures/cli-parity/cases.json
A deterministic differential flag fuzzer (walks the operation manifest, runs seeded flag combos through both CLIs against a mock, diffs exit/stdout/stderr/request) surfaced a class of required-argument validations the earlier exit-code pass missed: they returned plain errors (exit 1) where the Node CLI exits 2. Converted, each verified Node=2/Rust=2 against a live differential: - describe with no command id - memories set with no key - chat with no recipient - payloads push with no file path - payloads pull with no merkle root - search with no query - routes reorder with no --set - send/chat when no --from is given and no active verified outbound domain resolves (pick_default_from_address), plus the send shortcut's --from-required guard Regression-guarded: reply with no --from stays exit 0 (it defaults from the replied-to email and never hits the domain lookup), memories set with a key but no value stays exit 1, and describe of an unknown operation stays exit 1 (distinct from the missing-argument path). The shared resolve_outbound_from helper used by reply is deliberately left at exit 1. Eight lock-in fixtures assert exit 2 + empty stdout + request behavior on both CLIs (stderr wording is intentionally divergent so it isn't asserted). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A live end-to-end differential against a staging org (every command run
through both CLIs) found more missing-required-flag validations exiting 1
where the Node CLI exits 2: `send --to`, `org secrets set --key`, and
`reply --id`. The shared `flag_one(...).ok_or_else(|| anyhow!("Missing
required --{name}"))` helper was already usage_error in functions_commands
and the generated path, but still plain anyhow in org_secrets, wake, and
mail. Convert those three to crate::usage_error.
Verified each Node=2/Rust=2 against the live org; endpoints test (already
correct) stays 2 as a regression guard. Three lock-in fixtures assert exit
2 + empty stdout on both CLIs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # cli-rust/tests/config.rs # test-fixtures/cli-parity/cases.json
poiley
added a commit
that referenced
this pull request
Jul 22, 2026
…339) PR #335 removed compare:false and added stdout/stderr assertions to the 48 request-only "generated ... alias request" fixtures, but the conflict resolution when landing #332 reverted them via an ours-wins union. Re-apply the transformation: assert stdout "null\n" + empty stderr + full Node<->Rust cross-compare on each (the mocks already return {success:true,data:null}). make cli-parity: 485 cases green. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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
Replaces #331 (its base was force-rebased, making it unmergeable). The rebased port added a
CliErrorusage-error mechanism with exit 2; this PR finishes the job.What changed
crate::usage_error. TheCliErrormechanism covered generated commands and some friendly families, but unknown flags, unknown boolean/short flags, missing flag values, unexpected arguments, and unknown subcommands still exited 1 across agent, auth, domains, doctor, inbox, memories, org-secrets, payloads, search, emails, mail, wake, routes, functions, and payments.--signup-codewithout an email, missing--accept-terms, conflicting or absent verification-code sources,--code-from-stdinon a TTY. Also the unknown functions template name.main_entry: exit-code selection now searches the whole error chain forCliErrorinstead of downcasting only the outermost error, so a usage error wrapped bycontext()still exits 2. Regression test added.describeunknown operation,configvalue validation, doctor failures, pending-auth state mismatches, unreadable--code-from-file, unset--code-from-env, browser login denial.tests/payments.rs:payments charge --bogusexpectation updated from 1 to 2, consistent with its own missing-value assertion three lines down and with the Node CLI.tests/payloads.rs: added the same root-levelusage_errorstub the functions test already uses.Validation
make rust-cli-check(fmt, clippy-D warnings, doc, all tests)make cli-parity: 453/453 cases green, command-surface parity, operation coverage, help sweep with--compare-flags🤖 Generated with Claude Code