Skip to content

Commit 2f7149e

Browse files
committed
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
1 parent 06788cf commit 2f7149e

2 files changed

Lines changed: 51 additions & 1 deletion

File tree

command_run.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"unicode"
1010
)
1111

12+
type helpShownKey struct{}
13+
1214
func (cmd *Command) parseArgsFromStdin() ([]string, error) {
1315
type state int
1416
const (
@@ -176,6 +178,7 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context
176178

177179
cmd.isInError = true
178180
if cmd.checkHelp() {
181+
ctx = context.WithValue(ctx, helpShownKey{}, true)
179182
if cmd.parent == nil {
180183
_ = ShowRootCommandHelp(cmd)
181184
} else {
@@ -210,6 +213,7 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context
210213
}
211214

212215
if cmd.checkHelp() {
216+
ctx = context.WithValue(ctx, helpShownKey{}, true)
213217
return ctx, helpCommandAction(ctx, cmd)
214218
} else {
215219
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
234238

235239
if cmd.After != nil && !cmd.Root().shellCompletion {
236240
defer func() {
241+
if ctx.Value(helpShownKey{}) != nil {
242+
return
243+
}
237244
if err := cmd.After(ctx, cmd); err != nil {
238245
err = cmd.handleExitCoder(ctx, err)
239246

command_test.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1810,6 +1810,50 @@ func TestCommand_AfterFunc(t *testing.T) {
18101810
assert.Equal(t, 0, counts.SubCommand, "Subcommand not executed when expected")
18111811
}
18121812

1813+
func TestCommand_AfterNotCalledOnSubcommandHelp(t *testing.T) {
1814+
var afterCalled bool
1815+
cmd := &Command{
1816+
After: func(_ context.Context, _ *Command) error {
1817+
afterCalled = true
1818+
return nil
1819+
},
1820+
Commands: []*Command{
1821+
{
1822+
Name: "sub",
1823+
Action: func(context.Context, *Command) error {
1824+
return nil
1825+
},
1826+
},
1827+
},
1828+
}
1829+
1830+
err := cmd.Run(buildTestContext(t), []string{"app", "sub", "--help"})
1831+
require.NoError(t, err)
1832+
assert.False(t, afterCalled, "After should not be called when --help is passed to subcommand")
1833+
}
1834+
1835+
func TestCommand_AfterStillCalledOnNormalSubcommand(t *testing.T) {
1836+
var afterCalled bool
1837+
cmd := &Command{
1838+
After: func(_ context.Context, _ *Command) error {
1839+
afterCalled = true
1840+
return nil
1841+
},
1842+
Commands: []*Command{
1843+
{
1844+
Name: "sub",
1845+
Action: func(context.Context, *Command) error {
1846+
return nil
1847+
},
1848+
},
1849+
},
1850+
}
1851+
1852+
err := cmd.Run(buildTestContext(t), []string{"app", "sub"})
1853+
require.NoError(t, err)
1854+
assert.True(t, afterCalled, "After should be called on normal subcommand execution")
1855+
}
1856+
18131857
func TestCommandNoHelpFlag(t *testing.T) {
18141858
oldFlag := HelpFlag
18151859
defer func() {
@@ -2431,7 +2475,6 @@ func TestCommand_HelpFlagTakesPrecedenceOverParseErrors(t *testing.T) {
24312475
r.Contains(buf.String(), "foo")
24322476
r.NotContains(errBuf.String(), "Incorrect Usage")
24332477
})
2434-
<<<<<<< HEAD
24352478

24362479
t.Run("subcommand with bad flag shows Incorrect Usage and help", func(t *testing.T) {
24372480
r := require.New(t)

0 commit comments

Comments
 (0)