exclusive(true) waives a required arg but not a required group, making the exclusive arg unusable
Please complete the following tasks
- I have searched the discussions
- I have searched the open and rejected issues
Rust Version
rustc 1.97.1 (8bab26f4f 2026-07-14)
Clap Version
4.6.6 (main, af30442)
Minimal reproducible code
use clap::{Arg, ArgAction, ArgGroup, Command};
fn main() {
// A required ARG next to an exclusive flag: the exclusive flag works.
let arg_case = Command::new("prog")
.arg(Arg::new("excl").long("excl").exclusive(true).action(ArgAction::SetTrue))
.arg(Arg::new("a").long("a").required(true).action(ArgAction::SetTrue))
.try_get_matches_from(vec!["prog", "--excl"]);
println!("ARG-CASE -> {:?}", arg_case.map(|_| "Ok").map_err(|e| e.kind()));
// A required GROUP instead of a required arg: the exclusive flag stops working.
let group_case = Command::new("prog")
.arg(Arg::new("excl").long("excl").exclusive(true).action(ArgAction::SetTrue))
.arg(Arg::new("a").long("a").action(ArgAction::SetTrue))
.arg(Arg::new("b").long("b").action(ArgAction::SetTrue))
.group(ArgGroup::new("ab").args(["a", "b"]).required(true))
.try_get_matches_from(vec!["prog", "--excl"]);
println!("GROUP-CASE -> {:?}", group_case.map(|_| "Ok").map_err(|e| e.kind()));
// Following the usage line clap prints for the group case.
let obey = Command::new("prog")
.arg(Arg::new("excl").long("excl").exclusive(true).action(ArgAction::SetTrue))
.arg(Arg::new("a").long("a").action(ArgAction::SetTrue))
.arg(Arg::new("b").long("b").action(ArgAction::SetTrue))
.group(ArgGroup::new("ab").args(["a", "b"]).required(true))
.try_get_matches_from(vec!["prog", "--excl", "--a"]);
println!("OBEY-USAGE -> {:?}", obey.map(|_| "Ok").map_err(|e| e.kind()));
}Steps to reproduce the bug with the above code
cargo run
Actual Behaviour
ARG-CASE -> Ok("Ok")
GROUP-CASE -> Err(MissingRequiredArgument)
OBEY-USAGE -> Err(ArgumentConflict)The error printed for the group case is:
error: the following required arguments were not provided:
Usage: prog --excl <--a|--b>That usage line tells the user to pass --excl together with one of the group's members, which is exactly what exclusive(true) forbids. So no invocation using --excl succeeds: alone it is MissingRequiredArgument, and with --a it is ArgumentConflict. The exclusive flag cannot be used at all.
Expected Behaviour
GROUP-CASE -> Ok, matching ARG-CASE. An exclusive argument that is present should waive a required group the same way it waives a required arg.
Additional Context
Validator::validate_required computes is_exclusive_present once and then consults it in two of the three places that can report something missing:
clap_builder/src/parser/validator.rs:247— the arg branch:if !is_exclusive_present && !self.is_missing_required_ok(arg, conflicts)clap_builder/src/parser/validator.rs:313— the conditionally-required branch:if !is_exclusive_present && required- the group branch in between (roughly
:255-:270) does not consult it at all
grep -rn is_exclusive_present clap_builder/src/ returns only the definition and those two uses, so the group branch is the odd one out rather than a deliberate exception.
On whether waiving is the intended behaviour at all: #5957 raised the arg case and the conclusion there was that the parser is working correctly and the confusion belonged to the derive layer — so waiving a required arg is intended, which is what makes the group branch look like an oversight.
I do not think this overlaps #6050 (draft, for #4707): that one is about requires being bypassed by mutually exclusive groups, a different direction, though it does touch the same function.
Happy to open a PR adding !is_exclusive_present && to the group branch with tests, if that is the fix you would want. I held off because #5957 carries M-breaking-change / S-waiting-on-decision and this changes when an error is reported, so it seemed worth agreeing on first.
Debug Output
Not attached — the reproduction above is a plain try_get_matches_from on main, no debug feature needed. Happy to add --features debug output if useful.
Source: clap-rs/clap