a2aproject / a2aproject/a2a-rs
a2acli: report a malformed invocation as an error envelope, not clap prose
- Lingua principale
- Rust
- Stelle
- 75
- Fork
- 19
- Merge medio
- 11h 27m
- PR unite (30g)
- 21
Descrizione
## Summary
A malformed invocation exits `2` correctly, but reports itself in clap's own prose rather than the Appendix B error envelope:
```console
$ a2acli --bogus-flag card get
error: unexpected argument '--bogus-flag' found
Usage: a2acli [OPTIONS]
```
§11.4 is explicit that this is a CLI-local failure and must be machine-readable:
> Errors MUST be machine-readable (the error envelope in Appendix B) […] **CLI-local failures** are the conditions the protocol has no opinion on, because they happen before or outside any request: **a malformed flag**, an unresolvable `--agent-card` reference, an unreadable local card, no network. Those carry a symbolic `A2ACLI_ERR_` identifier from Appendix D.
So `a2acli --bogus-flag` should print `{"error":{"code":"A2ACLI_ERR_USAGE",…}}` on stderr, the way every other usage error already does (`--endpoint` without a transport, a bad `--a2a-version`, a malformed `--data-part`).
The cause is that `run_args` uses `Cli::command().get_matches_from(args)`, which prints and exits inside clap before any of our own error handling runs.
## Why
This is the one error class a program is most likely to hit while being wired up — a flag renamed, a typo in a generated invocation, an agent harness passing something the tool does not accept. A consumer that parses stderr for the envelope (which §11.4 says it may rely on unconditionally) gets unparseable prose in exactly that case, and cannot distinguish "you invoked me wrongly" from a crash.
It was missed in the original #164 audit: the exit code is right, so the row looked satisfied.
## The subtlety
clap reports `--help` and `-V/--version` through the same `Err` channel, with `ErrorKind::DisplayHelp` / `DisplayHelpOnMissingArgumentOrSubcommand` / `DisplayVersion`. Those are **not** failures: they must keep printing to **stdout** and exiting `0` (`A2ACLI_CLI_001`). Only genuine parse failures become envelopes.
## Scope
- [ ] Switch `run_args` to `try_get_matches_from` and handle `clap::Error` ourselves.
- [ ] `DisplayHelp`, `DisplayHelpOnMissingArgumentOrSubcommand` and `DisplayVersion`: print clap's rendered output to stdout and exit `0`, unchanged from today.
- [ ] Every other `ErrorKind`: emit the Appendix B envelope with `A2ACLI_ERR_USAGE` on stderr and exit `2`.
- [ ] Keep clap's own message as the envelope's `message` (it names the offending argument precisely), flattened to one line, and populate `hint` pointing at `--help`.
- [ ] Never print clap's usage block alongside the envelope — §11.4 wants one object, and stdout must stay empty on failure (§11.1).
- [ ] Tests: an unknown flag, a missing required argument, and an invalid enum value each produce a parseable envelope with `A2ACLI_ERR_USAGE` and exit 2; `--help` and `--version` still write to stdout and exit 0; `help` as a subcommand likewise.
## Requirements closed
| ID | Requirement |
| --- | --- |
| `A2ACLI_OUT_004` | Errors are machine-readable and consistent: CLI-local failures carry an `A2ACLI_ERR_*` code (§11.4, App. B, App. D) — the malformed-flag case |
Fourth Tier 1 residual, alongside #178, #179 and #180.
Guida per i contributori
Apri la guida per i contributori
Valutazione
Questa issue non è ancora stata valutata.