Skip to content

Incomplete escaping of fish generated completion script #6367

@Nahor

Description

@Nahor

Please complete the following tasks

Rust Version

rustc 1.95.0 (59807616e 2026-04-14)

Clap Version

4.6.1

Minimal reproducible code

I used clap_complete/examples/dynamic.rs.
I replaced the command name dynamic with dyn\amic (... clap::Command::new("dyn\\amic")...)

Details
fn command() -> clap::Command {
    clap::Command::new("dyn\\amic")
        .arg(
            clap::Arg::new("input")
                .long("input")
                .short('i')
                .value_hint(clap::ValueHint::FilePath),
        )
        .arg(
            clap::Arg::new("format")
                .long("format")
                .short('F')
                .value_parser(["json", "yaml", "toml"]),
        )
        .args_conflicts_with_subcommands(true)
}

fn main() {
    clap_complete::CompleteEnv::with_factory(command).complete();

    let cmd = command();
    let matches = cmd.get_matches();
    println!("{matches:#?}");
}

#[test]
fn verify_cli() {
    command().debug_assert();
}

Steps to reproduce the bug with the above code

  • Compile the app
  • If on non-Windows platforms, rename the app, putting backslashes (\) in the new name (e.g. my\app)
  • Run COMPLETE=fish ./target/debug/<app name>

Actual Behaviour

This will output something like:

complete --keep-order --exclusive --command "dyn\\amic" --arguments "(COMPLETE=fish "C:\\Users\\...t\\target\\debug\\my_app.exe" -- (commandline --current-process --tokenize --cut-at-cursor) (commandline --current-token))"

Expected Behaviour

The output should be:

complete --keep-order --exclusive --command "dyn\\\\amic" --arguments "(COMPLETE=fish "C:\\\\Users\\\\...t\\\\target\\\\debug\\\\my_app.exe" -- (commandline --current-process --tokenize --cut-at-cursor) (commandline --current-token))"

doubling the escaping for --command and --arguments.

Additionally, for --arguments, clap could use / instead of \ for the path separator, even on Windows. But that's not a full fix since non-Windows platforms can still have a \ in the directory/file names.

And alternatively for --command, don't explicitly use the option name (i.e. use just name instead of --command name).

Additional Context

The issue is because a first unescaping will be done when fish interprets the whole output (typically by source).

Then, for --arguments, there is a second unescaping that will be done during a completion, when fish interprets the --arguments value.

For --command, there is a second unescaping done by complete when processing its arguments. I believe it's a bug in fish, but I'm not certain (fish-shell/fish-shell#12712). When the --command is used implicitly, there is no 2nd unescaping.

Debug Output

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-completionArea: completion generatorC-bugCategory: bugE-easyCall for participation: Experience needed to fix: Easy / not much

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions