Skip to content

MutuallyExclusiveFlags not validated on subcommands for persistent flags #2273

@siutsin

Description

@siutsin

My urfave/cli version is

v3.7.0

Checklist

  • Are you running the latest v3 release? The list of releases is here.
  • Did you check the manual for your release? The v3 manual is here
  • Did you perform a search about this problem? Here's the GitHub guide about searching.

Dependency Management

  • My project is using go modules.

Describe the bug

This is a follow-up to #2265.

PR #2266 fixed persistent flag propagation for MutuallyExclusiveFlags, so subcommands can now read the flag values. However, the mutual exclusivity validation itself is not enforced on subcommands. When both flags from a mutually exclusive group defined on a parent command are passed to a subcommand, no error is returned.

The root cause is in command_run.go: the check only iterates over cmd.MutuallyExclusiveFlags (the current command's groups) rather than walking the parent chain.

To reproduce

cmd := &cli.Command{
    Name: "root",
    MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{
        {
            Flags: [][]cli.Flag{
                {&cli.StringFlag{Name: "alpha"}},
                {&cli.StringFlag{Name: "beta"}},
            },
        },
    },
    Commands: []*cli.Command{
        {
            Name: "sub",
            Action: func(_ context.Context, cmd *cli.Command) error {
                return nil
            },
        },
    },
}

err := cmd.Run(context.Background(), []string{"root", "sub", "--alpha", "hello", "--beta", "world"})
fmt.Println(err) // nil, no error returned

Observed behavior

cmd.Run returns nil. Both --alpha and --beta are accepted without error.

Expected behavior

cmd.Run should return an error containing "cannot be set along with", since --alpha and --beta belong to the same MutuallyExclusiveFlags group on the parent command.

Additional context

The fix is to walk the parent chain when checking mutually exclusive flag groups, similar to how persistent flag inheritance already walks the parent chain in parseFlags.

Want to fix this yourself?

Yes, see PR #2274.

Run go version and paste its output here

go version go1.26.0 darwin/arm64

Run go env and paste its output here

GO111MODULE='on'
GOARCH='arm64'
GOOS='darwin'
GOVERSION='go1.26.0'

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/v3relates to / is being considered for v3kind/bugdescribes or fixes a bugstatus/triagemaintainers still need to look into this

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions