Skip to content

Should option tokens be greedy with regard to subsequent bundled option or subcommand tokens? #1380

Open

Description

@jonsequitur

Currently, the token following a known option token is not treated as an argument. This means that if the subsequent token matches a subcommand, it will be treated as a subcommand

var optionA = new Option<string>("-a") { Arity = ArgumentArity.ZeroOrOne };
var optionB = new Option<bool>("-b");
var optionC = new Option<bool>("-c");

var command = new RootCommand
{
    optionA,
    optionB,
    optionC
};

var result = command.Parse("-a -bc"); // -> [ root [ -a ] [ -b <True> ] [ -c <True> ] ]
var optionA = new Option<string>("-a");
var root = new RootCommand
{
    new Command("subcommand"),
    optionA
};

var result = root.Parse("-a subcommand"); // -> [ root [ -a ] [ subcommand ] ]

Should this behavior be changed so that the subsequent token would be considered an argument to the option, producing the following results, respectively, for the above examples?

[ root [ -a <-bc> ]
[ root [ -a <subcommand> ]

Activity

  1. changed the title [-]Make option tokens greedy[/-] [+]Should option tokens be greedy?[/+] on Aug 18, 2021
  2. changed the title [-]Should option tokens be greedy?[/-] [+]Should option tokens be greedy with regard to subsequent bundled option or subcommand tokens?[/+] on Aug 18, 2021
  3. elgonzo commented on Aug 18, 2021

    @elgonzo
    Contributor

    In my opion the "greedy" behavior should be default.

    Otherwise, this has to be documented, including in the help text as generated by System.CommandLine. Imagine having to document/explain that the argument following an option has to be different from any other option/subcommand name, otherwise you, the user, has to invoke the commandline of the program in this or that other way. This not only can become akward and confusing. but it also makes the user responsible for checking whether the argument value they provided matches by chance a subcommand or another option. It might perhaps even make otherwise valid commandline invocations impossible.

    What if the user actually wants to assign the value "-b" to the option -a? What if the CLI in question should allow program invocation without invoking a subcommand (in other words, the root command has a handler) and the user wants to provide -a with a "subcommand" value? How could they do this if options are non-greedy?

    The greedy approach avoids all this. Documentation and help text remain relatively simple and there are no special cases involved where an argument value by chance collides with other option or subcommand names.

    For those who prefer the "special case"-ness of the non-greedy behavior, perhaps there could be a setting offered by System.CommandLine to enable non-greedy behavior if desired. (Perhaps as an extension method applicable to RootCommand instances which effects this setting on the root command, all its descendent sub commands and options.)

  4. ViktorHofer commented on Oct 29, 2026

    @ViktorHofer
    Member

    Data point from a real migration, plus an update on where this landed.

    Current behaviour is mixed

    Re-running both examples from the original post against 3.0.0-rc.3.26522.148 on .NET 10:

    [-a -bc]           -a=''                    errors=0
    [-a subcommand]    -a='subcommand'  cmd=<root>  errors=[Required command was not provided.]
    

    So the second example did change to the greedy behaviour proposed here, while the first did not — bundled short options are still not consumed. Worth noting since this issue is still open and reads as though neither had changed.

    Greediness also extends to registered options of the same command, including --help:

    var targetRepo = new Option<string>("--target-repo");
    var root = new RootCommand { targetRepo, new Option<bool>("--disabled") };
    [--target-repo --disabled]   --target-repo='--disabled'   errors=0
    [--target-repo --help]       --target-repo='--help'       errors=0
    [--target-repo --bogus]      --target-repo='--bogus'      errors=0
    

    Why this is a problem in practice

    We are migrating the darc CLI off CommandLineParser as a prerequisite for Native AOT (dotnet/arcade-services#6825). CommandLineParser rejected option-like values unless they were explicitly assigned with =:

    [--search=--foo]   OK  Search='--foo'
    [--search --foo]   ERR CommandLine.UnknownOptionError
    

    Under the current greedy behaviour, darc get-subscriptions --target-repo --help silently binds "--help" as a repository name and queries for it, rather than printing help. A user who forgets an argument gets a confusing empty result set instead of an error.

    I agree with @elgonzo that greedy is the right default — it is the only way to pass a value that happens to look like an option. But the all-or-nothing nature of it is the issue. There is currently no way to opt into stricter handling, so a CLI migrating from a stricter parser has to accept the regression.

    Request

    An opt-in strict mode, ideally settable per-option as well as inherited from the root command, that reports an error when a scalar option's value starts with a prefix character and was not explicitly assigned with =.

    That second half is the key qualifier, and it is currently impossible to express: the = is erased during tokenization, so --search=--foo and --search --foo are byte-identical in ArgumentResult.Tokens, OptionResult.IdentifierToken, and ParseResult.Tokens. I filed #2868 for that specific gap, since preserving the distinction is a prerequisite for any strict mode that is not also a usability regression.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions