Skip to content

Using ArgAction::Help and ArgAction::Version disables conflicts_with and exclusive #6062

Open
@ntrypoint

Description

@ntrypoint

Please complete the following tasks

Rust Version

rustc 1.88.0 (6b00bc388 2025-06-23)

Clap Version

4.5.40

Minimal reproducible code

main.rs:

#[allow(dead_code)]

use clap::{Arg, ArgAction, Command};

fn clap_parse() {
    let matches: clap::ArgMatches = Command::new("goto")
        .about("Jump between known directories")
        .author("My Name")
        .version(clap::crate_version!())

        // Because its not possible with clap to have
        //  goto [global options] [subcommand] [subcommand options] [positional args]
        // (at least from my understanding), use this structure:
        //  goto [subcommand] [global options + subcommand options] [positional args]
        .args_conflicts_with_subcommands(true)

        // I disable the auto-supplied stuff here because I want
        // to make help and version conflict with any other arguments
        // and need to know their ID (= create them) to do that
        .disable_help_subcommand(true)
        .disable_help_flag(true)
        .disable_version_flag(true)

        // The user can either use one of these subcommands ...
        .subcommand(
            Command::new("learn")
                .visible_alias("l")
                .arg(Arg::new("learn_dirs").num_args(1..).value_name("dir")),
        )
        .subcommand(
            Command::new("forget")
                .visible_alias("f")
                .arg(Arg::new("forget_dirs").num_args(1..).value_name("dir")),
        )
        .subcommand(
            Command::new("match")
                .visible_alias("m")
                .arg(Arg::new("match_dirs").num_args(1..).value_name("dir")),
        )
        .subcommand(
            Command::new("edit")
                .visible_alias("e")
                .arg(Arg::new("edit_dirs").num_args(1..).value_name("dir")),
        )

        // ... or no subcommand, which tries to change directory to "target".
        .arg(
            Arg::new("target")
                .num_args(1)
                .value_name("target")
                .conflicts_with_all(["help", "version"])
                .help("The query string to jump to"),
        )

        // These boolean/value flags should work in all cases:
        .arg(
            Arg::new("cwd")
                .global(true)
                .num_args(1)
                .short('C')
                .long("working-dir")
                .value_name("dir")
                .help("Work from this directory"),
        )
        .arg(
            Arg::new("verbose")
                .global(true)
                .short('v')
                .long("verbose")
                .action(ArgAction::SetTrue)
                .help("Print extra information"),
        )

        // Using --version should only be possible like this:
        //   goto --version <--no trailing stuff here-->
        .arg(
            Arg::new("version")
                .exclusive(true)
                .short('V')
                .long("version")
                .action(ArgAction::Version)
                .help("Print the version"),
        )

        // Using --help should only be possible like this:
        //   goto --help <--no trailing stuff here-->
        //   (and if possible also)  goto <subcommand> --help <--no trailing stuff here-->
        // Although that is a little silly since it would probably be
        // ok to display the help anyway. I'd prefer the message
        // saying the combination of arguments is illegal still.
        .arg(
            Arg::new("help")
                .exclusive(true)
                .short('h')
                .long("help")
                .action(ArgAction::Help)
                .help("Print the version"),
        )
        .get_matches();

    println!("Debug output: {:?}", matches);

    if matches.get_flag("verbose") {
        println!("verbose mode active");
    }
}

fn main() {
    clap_parse();
    println!("Hello, world!");
}

Steps to reproduce the bug with the above code

  1. Compile and run the above code to make sure it works
  2. Run the compiled binary with arguments cargo run -- foo --help or cargo run -- foo --version

Actual Behaviour

I observe that these inputs display the help / version just fine even though they should lead to an error based on what the builder code here reads. Something like goto Documents --version should just be an error, but it seems the target argument is assigned the string "Documents" and then the ´ArgAction::Version´ runs, even though --version is supposed to be exclusive.

Expected Behaviour

There should be a runtime error along the lines of error: the argument ... cannot be used with one or more of the other specified arguments.

Additional Context

I am aware that running help/version terminates early. However I still think this behaviour is confusing and wrong. Replacing the two ArgActions with the SetTrue action, everything just works as intended. But I would very much like to use the automatic help / version display... ☹️

Debug Output

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-helpArea: documentation, including docs.rs, readme, examples, etc...A-parsingArea: Parser's logic and needs it changed somehow.C-bugCategory: bugM-breaking-changeMeta: Implementing or merging this will introduce a breaking change.S-triageStatus: New; needs maintainer attention.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions