From c8d40bf7f5ccfc4773c13bf21f637aceea855767 Mon Sep 17 00:00:00 2001 From: Utkarsh Tiwari Date: Sun, 26 Jul 2026 22:46:07 +0530 Subject: [PATCH 1/2] fix: inherit HideHelpCommand in subcommands --- command_setup.go | 13 ++++++++++++- help_test.go | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/command_setup.go b/command_setup.go index 646e270ce7..52ecc06278 100644 --- a/command_setup.go +++ b/command_setup.go @@ -227,6 +227,17 @@ func (cmd *Command) hideHelp() bool { return false } +func (cmd *Command) hideHelpCommand() bool { + tracef("hide help command (cmd=%[1]q)", cmd.Name) + for c := cmd; c != nil; c = c.parent { + if c.HideHelpCommand { + return true + } + } + + return false +} + func (cmd *Command) ensureHelp() { tracef("ensuring help (cmd=%[1]q)", cmd.Name) @@ -234,7 +245,7 @@ func (cmd *Command) ensureHelp() { if !cmd.hideHelp() { if cmd.Command(helpCommand.Name) == nil { - if !cmd.HideHelpCommand { + if !cmd.hideHelpCommand() { tracef("appending helpCommand (cmd=%[1]q)", cmd.Name) cmd.appendCommand(helpCommand) } diff --git a/help_test.go b/help_test.go index d3c831371d..a125764853 100644 --- a/help_test.go +++ b/help_test.go @@ -1271,8 +1271,11 @@ func TestHideHelpCommand_WithHideHelp(t *testing.T) { } func TestHideHelpCommand_WithSubcommands(t *testing.T) { + out := &bytes.Buffer{} cmd := &Command{ HideHelpCommand: true, + Writer: out, + ErrWriter: out, Commands: []*Command{ { Name: "nully", @@ -1289,6 +1292,16 @@ func TestHideHelpCommand_WithSubcommands(t *testing.T) { r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "help"}), "No help topic for 'help'") r.NoError(cmd.Run(buildTestContext(t), []string{"cli.test", "--help"})) + + out.Reset() + r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "help"}), "No help topic for 'help'") + r.NoError(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "--help"})) + r.NotContains(out.String(), "help, h") + + out.Reset() + r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "nully2", "help"}), "No help topic for 'help'") + r.NoError(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "nully2", "--help"})) + r.NotContains(out.String(), "help, h") } func TestDefaultCompleteWithFlags(t *testing.T) { From db44817aa137887dc7d84baecf540a904a453610 Mon Sep 17 00:00:00 2001 From: Utkarsh Tiwari Date: Sun, 16 Aug 2026 03:04:19 +0530 Subject: [PATCH 2/2] Document that HideHelpCommand is inherited by subcommands The field now behaves like HideHelp: a true value applies to the whole subtree and a subcommand cannot turn it back off. Say so on the field and pin both halves of that behaviour with tests, so the shared semantics are not lost again. --- command.go | 3 +++ godoc-current.txt | 3 +++ help_test.go | 43 +++++++++++++++++++++++++++++++++++++++++ testdata/godoc-v3.x.txt | 3 +++ 4 files changed, 52 insertions(+) diff --git a/command.go b/command.go index 4cd907a558..f0cf9a715d 100644 --- a/command.go +++ b/command.go @@ -46,6 +46,9 @@ type Command struct { Flags []Flag `json:"flags"` // Boolean to hide built-in help command and help flag HideHelp bool `json:"hideHelp"` + // Boolean to hide the built-in help command. Applies to this command and + // all of its subcommands: as with HideHelp, a true value is inherited and + // a subcommand cannot turn it back off. // Ignored if HideHelp is true. HideHelpCommand bool `json:"hideHelpCommand"` // Boolean to hide built-in version flag and the VERSION section of help diff --git a/godoc-current.txt b/godoc-current.txt index d680c5f544..b3e9797175 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -464,6 +464,9 @@ type Command struct { Flags []Flag `json:"flags"` // Boolean to hide built-in help command and help flag HideHelp bool `json:"hideHelp"` + // Boolean to hide the built-in help command. Applies to this command and + // all of its subcommands: as with HideHelp, a true value is inherited and + // a subcommand cannot turn it back off. // Ignored if HideHelp is true. HideHelpCommand bool `json:"hideHelpCommand"` // Boolean to hide built-in version flag and the VERSION section of help diff --git a/help_test.go b/help_test.go index a125764853..b11a9d6458 100644 --- a/help_test.go +++ b/help_test.go @@ -1304,6 +1304,49 @@ func TestHideHelpCommand_WithSubcommands(t *testing.T) { r.NotContains(out.String(), "help, h") } +// A subcommand cannot re-enable the help command once an ancestor has hidden +// it. This mirrors HideHelp, which has always been inherited the same way. +func TestHideHelpCommand_SubcommandCannotOptBackIn(t *testing.T) { + out := &bytes.Buffer{} + cmd := &Command{ + HideHelpCommand: true, + Writer: out, + ErrWriter: out, + Commands: []*Command{ + { + Name: "nully", + HideHelpCommand: false, // ignored, the root already hid it + }, + }, + } + + r := require.New(t) + + r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "help"}), "No help topic for 'help'") + r.NoError(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "--help"})) + r.NotContains(out.String(), "help, h") +} + +// The same inheritance already applies to HideHelp, so the two fields stay +// consistent with each other. +func TestHideHelp_SubcommandCannotOptBackIn(t *testing.T) { + cmd := &Command{ + HideHelp: true, + Writer: io.Discard, + Commands: []*Command{ + { + Name: "nully", + HideHelp: false, // ignored, the root already hid it + }, + }, + } + + r := require.New(t) + + r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "help"}), "No help topic for 'help'") + r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "--help"}), providedButNotDefinedErrMsg) +} + func TestDefaultCompleteWithFlags(t *testing.T) { origArgv := os.Args t.Cleanup(func() { os.Args = origArgv }) diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index d680c5f544..b3e9797175 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -464,6 +464,9 @@ type Command struct { Flags []Flag `json:"flags"` // Boolean to hide built-in help command and help flag HideHelp bool `json:"hideHelp"` + // Boolean to hide the built-in help command. Applies to this command and + // all of its subcommands: as with HideHelp, a true value is inherited and + // a subcommand cannot turn it back off. // Ignored if HideHelp is true. HideHelpCommand bool `json:"hideHelpCommand"` // Boolean to hide built-in version flag and the VERSION section of help