Skip to content

HideHelpCommand: true on a parent command causes missing GLOBAL OPTIONS in subcommand #2268

@TimSoethout

Description

@TimSoethout

My urfave/cli version is v3.6.2

Checklist

  • Are you running the latest v3 release? The list of releases is here.
  • Did you check the manual for your release? The v3 manual is here
  • Did you perform a search about this problem? Here's the GitHub guide about searching.

Dependency Management

  • My project is using go modules.

Describe the bug

Setting HideHelpCommand: true on a parent command causes subcommand help to use SubcommandHelpTemplate instead of CommandHelpTemplate, which results in GLOBAL OPTIONS (persistent flags from the root command) not being displayed in subcommand help output.

The root cause is in helpCommandAction() in help.go. The template selection logic at lines 125-140 depends on len(cmd.Commands):

if (len(cmd.Commands) == 1 && !cmd.HideHelp) || (len(cmd.Commands) == 0 && cmd.HideHelp) {
    tmpl = CommandHelpTemplate  // Has GLOBAL OPTIONS
} else {
    ShowSubcommandHelp(cmd)     // Uses SubcommandHelpTemplate - NO GLOBAL OPTIONS
}

When HideHelpCommand: true is set:

  • ensureHelp() does NOT add the internal help subcommand to cmd.Commands
  • Subcommand ends up with len(Commands) == 0
  • With HideHelp = false (default), condition evaluates to false
  • Falls through to ShowSubcommandHelp() → uses SubcommandHelpTemplateGLOBAL OPTIONS missing

When HideHelpCommand: false (default):

  • ensureHelp() adds the internal help subcommand
  • Subcommand ends up with len(Commands) == 1
  • Condition evaluates to true
  • Uses CommandHelpTemplateGLOBAL OPTIONS displayed correctly

To reproduce

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/urfave/cli/v3"
)

func main() {
	app := &cli.Command{
		Name: "myapp",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:  "config",
				Usage: "Path to config file",
			},
		},
		HideHelpCommand: true, // This breaks GLOBAL OPTIONS in subcommand help
		Commands: []*cli.Command{
			{
				Name:  "serve",
				Usage: "Start the server",
				Action: func(ctx context.Context, c *cli.Command) error {
					fmt.Println("serving...")
					return nil
				},
			},
		},
	}

	if err := app.Run(context.Background(), os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

Run:

go run main.go serve --help

Observed behavior

NAME:
   myapp serve - Start the server

USAGE:
   myapp serve

OPTIONS:
   --help, -h  show help

Note: GLOBAL OPTIONS section is missing - the --config flag is not shown.

Expected behavior

NAME:
   myapp serve - Start the server

USAGE:
   myapp serve [options]

OPTIONS:
   --help, -h  show help

GLOBAL OPTIONS:
   --config string  Path to config file

The GLOBAL OPTIONS section should be displayed regardless of whether HideHelpCommand is set, because:

  1. HideHelpCommand is documented to hide the help subcommand, not affect help output format
  2. CommandHelpTemplate includes {{if .VisiblePersistentFlags}}GLOBAL OPTIONS:...{{end}}
  3. SubcommandHelpTemplate lacks this section entirely
  4. The choice between these templates should not depend on whether an internal help subcommand exists

Additional context

Workaround

Override cli.ShowSubcommandHelp to use CustomHelpTemplate when available:

cli.ShowSubcommandHelp = func(cmd *cli.Command) error {
    tmpl := cmd.CustomHelpTemplate
    if tmpl == "" {
        tmpl = cli.SubcommandHelpTemplate
    }
    cli.HelpPrinter(cmd.Root().Writer, tmpl, cmd)
    return nil
}

And set CustomHelpTemplate on commands to include the GLOBAL OPTIONS section.

Suggested fix

Either:

  1. Add {{if .VisiblePersistentFlags}}GLOBAL OPTIONS:{{template "visiblePersistentFlagTemplate" .}}{{end}} to SubcommandHelpTemplate
  2. Or change the template selection logic in helpCommandAction() to not depend on len(cmd.Commands) which is affected by HideHelpCommand
  3. Or make DefaultShowSubcommandHelp respect CustomHelpTemplate like DefaultShowCommandHelp does

Want to fix this yourself?

Yes, I'd be happy to submit a PR. The simplest fix would be to add the GLOBAL OPTIONS section to SubcommandHelpTemplate in template.go:

var SubcommandHelpTemplate = `NAME:
   {{template "helpNameTemplate" .}}

USAGE:
   ...existing template content...

OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .VisiblePersistentFlags}}

GLOBAL OPTIONS:{{template "visiblePersistentFlagTemplate" .}}{{end}}
`

Run go version and paste its output here

go version go1.24.4 darwin/arm64

Run go env and paste its output here

AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE='on'
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/user/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/user/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/x2/3fr4kvl139zc880zh91n0j_c0000gp/T/go-build3762887804=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/user/Developer/P09580-SimulationToolkit/go.mod'
GOMODCACHE='/Users/user/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/user/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/user/Developer/.anyenv/envs/goenv/versions/1.24.4'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/user/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/user/Developer/.anyenv/envs/goenv/versions/1.24.4/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.4'
GOWORK=''
PKG_CONFIG='pkg-config'

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/v3relates to / is being considered for v3kind/bugdescribes or fixes a bugstatus/triagemaintainers still need to look into this

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions