`--json` is silently ignored by about half the command surface #32
Links
No links yet.
--json is declared global = true (src/commands/mod.rs:36), so clap accepts it on every subcommand and commands::run sets the global flag (mod.rs:87). But only ~60 of ~140 leaf subcommands ever call output::is_json(). The rest print human text — often with ANSI bold/underline — and exit 0, so a script piping to jq gets garbage with no signal that anything went wrong.
Nouns where no subcommand honors --json
| Noun | Leaves affected |
|---|---|
auth |
login, status, logout |
release |
all 11 |
deploy |
list, create, status, env list, env create |
registry |
token create, token list, token delete |
ssh-key |
list, add, remove |
update |
the whole command |
Reproduced against the live server:
$ anvil ssh-key list --json
SSH Keys
ID NAME FINGERPRINT ...
Worse: nouns that honor it inconsistently within themselves
A user cannot form a rule, because sibling read commands disagree:
pr list/view/edithonor it;pr reviewsdoes not (src/commands/pr.rs:703-742)ci list/viewhonor it;ci secretsdoes not (src/commands/ci.rs:522-552)epic list/viewhonor it;epic childrendoes not (src/commands/epic.rs:276-302)issue move/link-req/unlink-reqemitjson_okenvelopes (issue.rs:915,:936,:956) whileissue link/unlinkimmediately above them do not
Suggested fix
Two parts, because the silent success is the actual danger:
- Make
--jsoncover every leaf that produces output. - Until then, have a leaf that ignores
--jsonfail loudly rather than print human text — an unhandled--jsonshould be an error, not a no-op.
Found during a CLI-wide consistency audit.
Fixed by #48 (merged).
Verified on main: every leaf command now returns an `output::Response` that `commands::run` emits under `–json` — including all the nouns listed here (`auth`, `release`, `deploy`, `registry`, `ssh-key`, `update`). The within-noun inconsistencies (`pr reviews`, `ci secrets`, `epic children`, `issue link`/`unlink`) are gone too.
The “fail loudly rather than silently succeed” half is now structural rather than a runtime check: a leaf cannot compile without returning a Response, clippy.toml bans raw println!/print! outside the renderer, and tests/json_contract.rs::every_leaf_is_covered walks the clap tree and fails CI if a new subcommand appears without being wired through the contract. tests/json_fuzz.rs additionally runs 120 leaves under --json against a mock and asserts stdout is exactly one valid JSON value.