From 026a324cabbdf4079273cdde99d641c57c5eabe6 Mon Sep 17 00:00:00 2001 From: Dean Chen <862469039@qq.com> Date: Fri, 4 Sep 2026 18:16:15 +0500 Subject: [PATCH 1/2] cmd: report unknown command before unknown flag `docker iamges --filter` was coming back as unknown flag because the root command kept parsing flags after the first token. Stop at the first non-flag so this matches `docker iamges` and says the command is unknown. Fixes #4550 Signed-off-by: Dean Chen <862469039@qq.com> --- cmd/docker/docker.go | 4 ++++ cmd/docker/docker_test.go | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index 2341570b91a1..237b2f53e85b 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -186,6 +186,10 @@ func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand { cmd.SetOut(dockerCli.Out()) commands.AddCommands(cmd, dockerCli) + // Stop root flag parsing at the first non-flag so a misspelled command + // followed by flags is reported as an unknown command, not "unknown flag". + cmd.Flags().SetInterspersed(false) + visitAll(cmd, setValidateArgs(dockerCli)) // flags must be the top-level command flags, not cmd.Flags() diff --git a/cmd/docker/docker_test.go b/cmd/docker/docker_test.go index e68ba48db92b..20b523d1ec14 100644 --- a/cmd/docker/docker_test.go +++ b/cmd/docker/docker_test.go @@ -92,6 +92,15 @@ func TestExitStatusForInvalidSubcommand(t *testing.T) { assert.Check(t, is.ErrorContains(err, "docker: unknown command: docker invalid")) } +func TestUnknownCommandWithFlag(t *testing.T) { + err := runCliCommand(t, nil, nil, "iamges", "--filter") + assert.Check(t, is.ErrorContains(err, "docker: unknown command: docker iamges")) + assert.Check(t, is.ErrorContains(err, "docker --help")) + + err = runCliCommand(t, nil, nil, "iamges", "-f") + assert.Check(t, is.ErrorContains(err, "docker: unknown command: docker iamges")) +} + func TestVersion(t *testing.T) { var b bytes.Buffer err := runCliCommand(t, nil, &b, "--version") From 94a9bcc714d003b2a2e8eaf9da2a19f1376e0d41 Mon Sep 17 00:00:00 2001 From: Dean Chen <862469039@qq.com> Date: Sat, 5 Sep 2026 00:03:06 +0500 Subject: [PATCH 2/2] cmd: keep --help working after an unknown command SetInterspersed(false) made `docker nonexistent --help` fail instead of printing the root help. Rewrite unknown-flag errors only when the first leftover arg isn't a subcommand. Fixes #4550 Signed-off-by: Dean Chen <862469039@qq.com> --- cmd/docker/docker.go | 34 ++++++++++++++++++++++++++++++---- cmd/docker/docker_test.go | 11 +++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index 237b2f53e85b..2d65ea4d79bb 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -186,10 +186,6 @@ func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand { cmd.SetOut(dockerCli.Out()) commands.AddCommands(cmd, dockerCli) - // Stop root flag parsing at the first non-flag so a misspelled command - // followed by flags is reported as an unknown command, not "unknown flag". - cmd.Flags().SetInterspersed(false) - visitAll(cmd, setValidateArgs(dockerCli)) // flags must be the top-level command flags, not cmd.Flags() @@ -206,10 +202,40 @@ func setFlagErrorFunc(dockerCli command.Cli, cmd *cobra.Command) { if err := isSupported(cmd, dockerCli); err != nil { return err } + if cmd == cmd.Root() { + if rewritten := unknownCommandInsteadOfFlag(cmd, err); rewritten != nil { + return rewritten + } + } return flagErrorFunc(cmd, err) }) } +// unknownCommandInsteadOfFlag rewrites "unknown flag" to "unknown command" +// when the first leftover arg isn't a docker subcommand. That way +// `docker iamges --filter` matches `docker iamges` instead of complaining +// about --filter. `--help` after a bogus name still works because it is a +// known flag and never reaches FlagErrorFunc. +func unknownCommandInsteadOfFlag(cmd *cobra.Command, err error) error { + msg := err.Error() + if !strings.HasPrefix(msg, "unknown flag:") && !strings.HasPrefix(msg, "unknown shorthand flag:") { + return nil + } + args := cmd.Flags().Args() + if len(args) == 0 { + return nil + } + name := args[0] + if strings.HasPrefix(name, "-") { + return nil + } + found, _, _ := cmd.Find([]string{name}) + if found != nil && found != cmd { + return nil + } + return fmt.Errorf("docker: unknown command: docker %s\n\nRun 'docker --help' for more information", name) +} + func setupHelpCommand(dockerCli command.Cli, rootCmd, helpCmd *cobra.Command) { origRun := helpCmd.Run origRunE := helpCmd.RunE diff --git a/cmd/docker/docker_test.go b/cmd/docker/docker_test.go index 20b523d1ec14..58004f4af511 100644 --- a/cmd/docker/docker_test.go +++ b/cmd/docker/docker_test.go @@ -93,14 +93,21 @@ func TestExitStatusForInvalidSubcommand(t *testing.T) { } func TestUnknownCommandWithFlag(t *testing.T) { - err := runCliCommand(t, nil, nil, "iamges", "--filter") + err := runCliCommand(t, discard, nil, "iamges", "--filter") assert.Check(t, is.ErrorContains(err, "docker: unknown command: docker iamges")) assert.Check(t, is.ErrorContains(err, "docker --help")) - err = runCliCommand(t, nil, nil, "iamges", "-f") + err = runCliCommand(t, discard, nil, "iamges", "-f") assert.Check(t, is.ErrorContains(err, "docker: unknown command: docker iamges")) } +func TestUnknownCommandWithHelpFlag(t *testing.T) { + var b bytes.Buffer + err := runCliCommand(t, discard, &b, "nonexistent", "--help") + assert.NilError(t, err) + assert.Check(t, is.Contains(b.String(), "Usage:")) +} + func TestVersion(t *testing.T) { var b bytes.Buffer err := runCliCommand(t, nil, &b, "--version")