--output json worked everywhere. On the top-level command, on every ordinary subcommand, wherever the user fancied putting it. Then it stopped working in exactly one place, and of course it was the subcommand I’d been clever about.
How the global flag is meant to work
clap has a lovely feature for this. Define --output text|json once at the top, mark it global = true, and it’s reachable from every subcommand: mytool --output json widget and mytool widget --output json land the same. You stop thinking about it.
The one place it goes missing
One subcommand, credentials, is a passthrough: it sets subcommand_passthrough = true, which makes clap capture everything after the subcommand name as trailing_var_arg and hand it on, the way cargo run -- ... passes the trailing args to your program rather than to cargo. The handler then re-parses those captured tokens against its own clap definition.
The trouble is that the captured tokens include --output. clap’s global = true propagation doesn’t reach a passthrough subtree, because the post-name tokens are taken as trailing_var_arg before the outer parser ever sees them. So in this one subtree the global flag isn’t applied, and worse, when the inner parser re-parses the captured args it meets --output, which it doesn’t define, and rejects it as unknown. The code says so where it matters, in crates/rtb-cli/src/credentials.rs:
// clap's outer `global = true` propagation works for normal
// subcommands, but `subcommand_passthrough = true` captures
// post-name tokens as `trailing_var_arg`, so the global
// never reaches the outer parser for this subtree.
args = strip_global_output(args);
Parse it yourself, then strip it
The fix is two moves. First, parse --output out of the raw args by hand (there’s an OutputMode::from_args_os for exactly that), so the output mode is still honoured. Then strip --output out of the args before the inner parser runs, so the inner clap doesn’t choke on a flag it doesn’t define. strip_global_output is the second move, from crates/rtb-cli/src/render.rs:
if s.starts_with("--output=") {
continue; // inline form: drop just this token
}
if s == "--output" {
iter.next(); // space-separated form: drop the token and its value
continue;
}
It handles both --output=json and --output json, and it’s idempotent, so it’s safe to call whether or not the flag is actually present.
The takeaway
global = true and trailing_var_arg are both “grab the args” features, and in a passthrough subcommand they reach for the same tokens. clap won’t arbitrate that overlap, and shouldn’t try to guess. So you arbitrate: parse the global out of the raw args yourself, strip it before you re-parse the rest, and the flag that “works everywhere” actually does.
