Skip to content

fix: preserve incomplete short groups for shell completion - #2485

Merged
Juneezee merged 1 commit into
urfave:mainfrom
kimjooyoon:fix/completion-short-groups
Oct 25, 2026
Merged

Juneezee merged 1 commit into
urfave:mainfrom
kimjooyoon:fix/completion-short-groups

Conversation

@kimjooyoon

@kimjooyoon kimjooyoon commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

What type of PR is this?

  • bug

What this PR does / why we need it:

With UseShortOptionHandling enabled, custom shell completion loses an unfinished grouped flag from cmd.Args(). For example, declare boolean a and string b, then run:

cmd.Run(ctx, []string{"app", "value", "-ab", "--generate-shell-completion"})

A ShellComplete callback reading cmd.Args().Slice() currently receives only ["value"]. After this change it receives ["value", "-ab"], matching the existing handling of an unfinished single flag such as -b.

command_parse.go retains the unparsed tail during shell completion when a short group contains an unknown flag, has a value-taking flag that is not last (-ba), or its final value-taking flag has no value yet. Normal invocations still return their original errors. Complete groups, including an explicit empty value (-ab=), remain consumed normally.

TestCommand_Run_CustomShellCompleteAcceptsMalformedFlags covers each of these groups, and a complete group that is still consumed during completion.

Which issue(s) this PR fixes:

Independently reproduced on main 10f69fd3c38de86a3bc17810dcb7f818b494f027; no existing matching issue or open PR found in the search before submission.

Special notes for your reviewer:

Authored and tested with a Codex agent on behalf of kimjooyoon. This change adds no dependency and changes no public API. It extends the existing single-flag completion behavior to grouped short flags.

Testing

Rebased onto main 63c2771c704accdd5f07eb40006c918da9687372:

  • The new -an, -ax and -na cases fail on main and pass with the fix.
  • go test ./..., go test -tags urfave_cli_no_template ./..., go test -race ./..., go vet ./... and golangci-lint pass.

Release Notes

Preserve unfinished grouped short flags in arguments passed to custom shell completion callbacks.

Authored with Codex. Keep pending grouped tokens available to custom
completion callbacks, matching the existing single-flag behavior.

Signed-off-by: Eng Zer Jun <[email protected]>
@Juneezee
Juneezee force-pushed the fix/completion-short-groups branch from 5dbdb10 to f529377 Compare October 25, 2026 05:50

@Juneezee Juneezee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This also fixes the default completer on a subcommand: with a bool flag all aliased a, app sub -al<TAB> used to suggest subcommands and now suggests --all.

I rebased onto main and pushed two changes:

  1. Completion now also keeps a group whose value-taking flag is not last, such as -ba. Before this, with flags a (bool), b (string) and batch (bool), app sub -ba<TAB> suggested subcommands instead of --batch, and a custom callback got []:

cli/command_parse.go

Lines 275 to 284 in f529377

} else {
// only the last flag of a group can take an argument, so a flag
// that needs one has nothing to read it from here
tracef("processing flag.5 (fName=%[1]q)", string(c))
if cmd.Root().shellCompletion {
posArgs = append(posArgs, rargs...)
return &stringSliceArgs{posArgs}, nil
}
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, string(c))
}

  1. I replaced completion_short_group_test.go with four cases in the existing completion test. Most of the old file re-tested normal group parsing that other tests already cover:

cli/command_test.go

Lines 603 to 617 in f529377

func TestCommand_Run_CustomShellCompleteAcceptsMalformedFlags(t *testing.T) {
cases := []struct {
testArgs *stringSliceArgs
expectedOut string
}{
{testArgs: &stringSliceArgs{v: []string{"--undefined"}}, expectedOut: "found 1 args"},
{testArgs: &stringSliceArgs{v: []string{"--number"}}, expectedOut: "found 1 args"},
{testArgs: &stringSliceArgs{v: []string{"--number", "forty-two"}}, expectedOut: "found 0 args"},
{testArgs: &stringSliceArgs{v: []string{"--number", "42"}}, expectedOut: "found 0 args"},
{testArgs: &stringSliceArgs{v: []string{"--number", "42", "newArg"}}, expectedOut: "found 1 args"},
{testArgs: &stringSliceArgs{v: []string{"-an"}}, expectedOut: "found 1 args"},
{testArgs: &stringSliceArgs{v: []string{"-ax"}}, expectedOut: "found 1 args"},
{testArgs: &stringSliceArgs{v: []string{"-na"}}, expectedOut: "found 1 args"},
{testArgs: &stringSliceArgs{v: []string{"-an", "42"}}, expectedOut: "found 0 args"},
}

I also updated the PR description to match.

@kimjooyoon

Copy link
Copy Markdown
Contributor Author

Thanks for catching the non-final value-taking case and consolidating the tests. I verified f529377 with root-only UseShortOptionHandling on a subcommand: default completion returns --all for -al and --batch for -ba, and custom callbacks receive the exact original tokens. Action stays uncalled in all four cases. The older submitted revision already handled -al; your additional guard closes the -ba gap in my original change.

@Juneezee
Juneezee merged commit a715e62 into urfave:main Oct 25, 2026
9 checks passed
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.

2 participants