From 06788cfbec77049ba7938c972db0a74b3a84a4dd Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 8 Jun 2026 05:39:14 -0400 Subject: [PATCH 1/2] test: add tests for --help taking precedence over parse errors; fix subcommand help display --- command_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/command_test.go b/command_test.go index acc2405614..28e69da651 100644 --- a/command_test.go +++ b/command_test.go @@ -2431,6 +2431,7 @@ func TestCommand_HelpFlagTakesPrecedenceOverParseErrors(t *testing.T) { r.Contains(buf.String(), "foo") r.NotContains(errBuf.String(), "Incorrect Usage") }) +<<<<<<< HEAD t.Run("subcommand with bad flag shows Incorrect Usage and help", func(t *testing.T) { r := require.New(t) From 2f7149e0b9a543f1e6e989661e5efc137836c73b Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 8 Jun 2026 15:52:04 -0400 Subject: [PATCH 2/2] fix: skip After hook when help is displayed on subcommand When --help is passed to a subcommand, the parent command's After hook was firing even though the parent's Before hook was never called. This fix tracks help invocation via a context key and skips the After hook when help was the reason for early return. Fixes #2250 --- command_run.go | 7 +++++++ command_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/command_run.go b/command_run.go index b5bc005a3c..8d5907151e 100644 --- a/command_run.go +++ b/command_run.go @@ -9,6 +9,8 @@ import ( "unicode" ) +type helpShownKey struct{} + func (cmd *Command) parseArgsFromStdin() ([]string, error) { type state int const ( @@ -176,6 +178,7 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context cmd.isInError = true if cmd.checkHelp() { + ctx = context.WithValue(ctx, helpShownKey{}, true) if cmd.parent == nil { _ = ShowRootCommandHelp(cmd) } else { @@ -210,6 +213,7 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context } if cmd.checkHelp() { + ctx = context.WithValue(ctx, helpShownKey{}, true) return ctx, helpCommandAction(ctx, cmd) } else { tracef("no help is wanted (cmd=%[1]q)", cmd.Name) @@ -234,6 +238,9 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context if cmd.After != nil && !cmd.Root().shellCompletion { defer func() { + if ctx.Value(helpShownKey{}) != nil { + return + } if err := cmd.After(ctx, cmd); err != nil { err = cmd.handleExitCoder(ctx, err) diff --git a/command_test.go b/command_test.go index 28e69da651..6b8d93390a 100644 --- a/command_test.go +++ b/command_test.go @@ -1810,6 +1810,50 @@ func TestCommand_AfterFunc(t *testing.T) { assert.Equal(t, 0, counts.SubCommand, "Subcommand not executed when expected") } +func TestCommand_AfterNotCalledOnSubcommandHelp(t *testing.T) { + var afterCalled bool + cmd := &Command{ + After: func(_ context.Context, _ *Command) error { + afterCalled = true + return nil + }, + Commands: []*Command{ + { + Name: "sub", + Action: func(context.Context, *Command) error { + return nil + }, + }, + }, + } + + err := cmd.Run(buildTestContext(t), []string{"app", "sub", "--help"}) + require.NoError(t, err) + assert.False(t, afterCalled, "After should not be called when --help is passed to subcommand") +} + +func TestCommand_AfterStillCalledOnNormalSubcommand(t *testing.T) { + var afterCalled bool + cmd := &Command{ + After: func(_ context.Context, _ *Command) error { + afterCalled = true + return nil + }, + Commands: []*Command{ + { + Name: "sub", + Action: func(context.Context, *Command) error { + return nil + }, + }, + }, + } + + err := cmd.Run(buildTestContext(t), []string{"app", "sub"}) + require.NoError(t, err) + assert.True(t, afterCalled, "After should be called on normal subcommand execution") +} + func TestCommandNoHelpFlag(t *testing.T) { oldFlag := HelpFlag defer func() { @@ -2431,7 +2475,6 @@ func TestCommand_HelpFlagTakesPrecedenceOverParseErrors(t *testing.T) { r.Contains(buf.String(), "foo") r.NotContains(errBuf.String(), "Incorrect Usage") }) -<<<<<<< HEAD t.Run("subcommand with bad flag shows Incorrect Usage and help", func(t *testing.T) { r := require.New(t)