Skip to content

command: stop Traverse at -- so a positional isn't run as a subcommand - #2532

Open
piyush295 wants to merge 1 commit into
spf13:mainfrom
piyush295:fix/traverse-stop-at-double-dash
Open

piyush295 wants to merge 1 commit into
spf13:mainfrom
piyush295:fix/traverse-stop-at-double-dash

Conversation

@piyush295

Copy link
Copy Markdown

Bug (fixes #2528)

With TraverseChildren: true, the long-flag branch of Command.Traverse matched a bare -- as a flag that "takes a value":

case strings.HasPrefix(arg, "--") && !strings.Contains(arg, "="):
    inFlag = !hasNoOptDefVal(arg[2:], c.Flags())
    flags = append(flags, arg)
    continue

arg[2:] is "", no flag by that name exists, so inFlag = true. The next positional is consumed as that flag's "value" and the following positional happens to match a subcommand — so the child handler runs, the first post--- positional is silently dropped, and the parent's positionals never reach any command.

Reproduction from the issue:

root := &cobra.Command{Use:"app", TraverseChildren:true, Args:cobra.ArbitraryArgs,
    Run: func(_ *cobra.Command, args []string){ fmt.Printf("root called with %q\\n", args) }}
root.AddCommand(&cobra.Command{Use:"child",
    Run: func(*cobra.Command, []string){ fmt.Println("child called") }})
root.Execute()
$ ./app -- literal child
child called                 # bug: wrong handler, "literal" lost
$ # expected:
root called with ["literal" "child"]

The default (non-traverse) stripFlags path already stops command discovery on --.

Fix

Mirror that behaviour. When Traverse sees a bare --:

  1. Parse any flags accumulated so far on the current command.
  2. Return the current command along with the remaining args starting at the delimiter, so a downstream parser sees the exact slice it would have gotten without TraverseChildren.

Test

TestTraverseStopsAtEndOfFlags covers two cases:

  • []string{"--", "literal", "child"} → traversal stays on root, remaining is unchanged.
  • []string{"--", "child"} → traversal stays on root, remaining is unchanged.

go test ./... is green.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

With TraverseChildren, the long-flag branch of Traverse matched a bare
-- as a flag that takes a value. It appended -- to the accumulated
flags, set inFlag=true (because arg[2:] is empty and no flag with that
name exists), consumed the next positional as the flag's value, and
then let the following positional match a subcommand name. The child
handler then ran, the first post-`--` positional was silently dropped,
and the parent's positionals never reached the chosen command.

Mirror the behaviour of the default stripFlags path and stop command
discovery as soon as -- is seen. Return the current command and the
remaining args starting at the delimiter so a downstream parser sees
the exact slice it would have gotten without TraverseChildren.

Fixes spf13#2528.
@piyush295
piyush295 force-pushed the fix/traverse-stop-at-double-dash branch from c783ca0 to 944515d Compare October 16, 2026 09:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TraverseChildren can execute a subcommand after -- and discard positional arguments

2 participants