From 607da84b2a45a86ef0b6d1e3e4aa8354b746f5e1 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 7 Jun 2026 21:48:16 -0400 Subject: [PATCH 1/4] fix: let --help flag take precedence over parse errors When --help is explicitly provided alongside bad flags, the help output should be shown instead of printing 'Incorrect Usage'. Also update stale comments in helpCommandAction to reflect all actual invocation paths. --- command_run.go | 10 ++++++++++ help.go | 22 +++++++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/command_run.go b/command_run.go index 5549e25270..a3e7408232 100644 --- a/command_run.go +++ b/command_run.go @@ -175,6 +175,16 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context deferErr = err cmd.isInError = true + + if cmd.checkHelp() { + if cmd.parent == nil { + _ = ShowRootCommandHelp(cmd) + } else { + _ = ShowCommandHelp(ctx, cmd, cmd.Name) + } + return ctx, nil + } + if cmd.OnUsageError != nil { err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil) err = cmd.handleExitCoder(ctx, err) diff --git a/help.go b/help.go index 7b7b4097d0..4bedf87d5d 100644 --- a/help.go +++ b/help.go @@ -86,14 +86,22 @@ func helpCommandAction(ctx context.Context, cmd *Command) error { tracef("doing help for cmd %[1]q with args %[2]q", cmd, args) - // This action can be triggered by a "default" action of a command - // or via cmd.Run when cmd == helpCmd. So we have following possibilities + // helpCommandAction is triggered in several ways: // - // 1 $ app - // 2 $ app help - // 3 $ app foo - // 4 $ app help foo - // 5 $ app foo help + // * the command has no user-defined Action (default action fallback) + // * the --help / -h flag was parsed (via cmd.checkHelp()) + // * the "help" subcommand (or "h" alias) was dispatched + // + // Possible invocations: + // + // $ app # default action; show root help + // $ app --help / -h # flag; show root help (ignores subsequent args) + // $ app help / h # subcommand; show root help + // $ app help / h foo # subcommand; show help for subcommand "foo" + // $ app --help / -h foo # flag; show help for subcommand "foo" + // $ app foo --help / -h # flag on subcommand; show help for "foo" + // $ app foo help / h # subcommand on subcommand; show help for "foo" + // $ app foo (no action) # default action on subcommand; show help for "foo" // Case 4. when executing a help command set the context to parent // to allow resolution of subsequent args. This will transform From 27d8d514adb8db1f4b6e0f80d0e5ff6861f7cbdb Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 8 Jun 2026 05:39:14 -0400 Subject: [PATCH 2/4] test: add tests for --help taking precedence over parse errors; fix subcommand help display --- command_run.go | 8 +++--- command_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/command_run.go b/command_run.go index a3e7408232..98a61f1a69 100644 --- a/command_run.go +++ b/command_run.go @@ -180,7 +180,7 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context if cmd.parent == nil { _ = ShowRootCommandHelp(cmd) } else { - _ = ShowCommandHelp(ctx, cmd, cmd.Name) + _ = ShowSubcommandHelp(cmd) } return ctx, nil } @@ -203,10 +203,8 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context tracef("SILENTLY IGNORING ERROR running ShowRootCommandHelp %[1]v (cmd=%[2]q)", err, cmd.Name) } } else { - tracef("running ShowCommandHelp with %[1]q", cmd.Name) - if err := ShowCommandHelp(ctx, cmd.parent, cmd.Name); err != nil { - tracef("SILENTLY IGNORING ERROR running ShowCommandHelp with %[1]q %[2]v", cmd.Name, err) - } + tracef("running ShowSubcommandHelp for %[1]q", cmd.Name) + _ = ShowSubcommandHelp(cmd) } } diff --git a/command_test.go b/command_test.go index 2a71504535..d2a9fa9f08 100644 --- a/command_test.go +++ b/command_test.go @@ -2363,6 +2363,76 @@ func TestCommand_OrderOfOperations(t *testing.T) { }) } +func TestCommand_HelpFlagTakesPrecedenceOverParseErrors(t *testing.T) { + t.Run("root command with --help and bad flag", func(t *testing.T) { + r := require.New(t) + var buf bytes.Buffer + var errBuf bytes.Buffer + + cmd := &Command{ + Writer: &buf, + ErrWriter: &errBuf, + Action: func(ctx context.Context, cmd *Command) error { + return nil + }, + } + + err := cmd.Run(buildTestContext(t), []string{"command", "--help", "--undefined"}) + r.NoError(err) + r.Contains(buf.String(), "NAME:") + r.Contains(buf.String(), "command") + r.NotContains(errBuf.String(), "Incorrect Usage") + }) + + t.Run("root command with -h and bad flag", func(t *testing.T) { + r := require.New(t) + var buf bytes.Buffer + var errBuf bytes.Buffer + + cmd := &Command{ + Writer: &buf, + ErrWriter: &errBuf, + Action: func(ctx context.Context, cmd *Command) error { + return nil + }, + } + + err := cmd.Run(buildTestContext(t), []string{"command", "-h", "--undefined"}) + r.NoError(err) + r.Contains(buf.String(), "NAME:") + r.Contains(buf.String(), "command") + r.NotContains(errBuf.String(), "Incorrect Usage") + }) + + t.Run("subcommand with --help and bad flag", func(t *testing.T) { + r := require.New(t) + var buf bytes.Buffer + var errBuf bytes.Buffer + + cmd := &Command{ + Writer: &buf, + ErrWriter: &errBuf, + Action: func(ctx context.Context, cmd *Command) error { + return nil + }, + Commands: []*Command{ + { + Name: "foo", + Action: func(ctx context.Context, cmd *Command) error { + return nil + }, + }, + }, + } + + err := cmd.Run(buildTestContext(t), []string{"command", "foo", "--help", "--undefined"}) + r.NoError(err) + r.Contains(buf.String(), "NAME:") + r.Contains(buf.String(), "foo") + r.NotContains(errBuf.String(), "Incorrect Usage") + }) +} + func TestFlagActionOrder(t *testing.T) { tests := []struct { Name string From b76d804bc4b5f3db676785201755ccab94647795 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 8 Jun 2026 07:20:50 -0400 Subject: [PATCH 3/4] test: cover subcommand error path for help display --- command_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/command_test.go b/command_test.go index d2a9fa9f08..acc2405614 100644 --- a/command_test.go +++ b/command_test.go @@ -2431,6 +2431,34 @@ func TestCommand_HelpFlagTakesPrecedenceOverParseErrors(t *testing.T) { r.Contains(buf.String(), "foo") r.NotContains(errBuf.String(), "Incorrect Usage") }) + + t.Run("subcommand with bad flag shows Incorrect Usage and help", func(t *testing.T) { + r := require.New(t) + var buf bytes.Buffer + var errBuf bytes.Buffer + + cmd := &Command{ + Writer: &buf, + ErrWriter: &errBuf, + Action: func(ctx context.Context, cmd *Command) error { + return nil + }, + Commands: []*Command{ + { + Name: "foo", + Action: func(ctx context.Context, cmd *Command) error { + return nil + }, + }, + }, + } + + err := cmd.Run(buildTestContext(t), []string{"command", "foo", "--undefined"}) + r.Error(err) + r.Contains(buf.String(), "NAME:") + r.Contains(buf.String(), "foo") + r.Contains(errBuf.String(), "Incorrect Usage") + }) } func TestFlagActionOrder(t *testing.T) { From 3a5e3e1f0a8ce70ef3872892a6044612dd8c5553 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 8 Jun 2026 08:18:28 -0400 Subject: [PATCH 4/4] chore: remove blank lines around checkHelp block to reduce diff size --- command_run.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/command_run.go b/command_run.go index 98a61f1a69..b5bc005a3c 100644 --- a/command_run.go +++ b/command_run.go @@ -175,7 +175,6 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context deferErr = err cmd.isInError = true - if cmd.checkHelp() { if cmd.parent == nil { _ = ShowRootCommandHelp(cmd) @@ -184,7 +183,6 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context } return ctx, nil } - if cmd.OnUsageError != nil { err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil) err = cmd.handleExitCoder(ctx, err)