Skip to content

cmd.Hidden is not recursive #2372

Open
Open

Description

@TLINDEN

My urfave/cli version is

v3@f980ca8

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

I am using the new Walk() feature by @dearchap to generate a tree of supported commands of my tool. This works great.

However, I want to exclude Hidden commands from the output. I can query the Hidden flag and do that, however, subcommands of a hidden command show up anyway.

To reproduce

func Tree(cmd *cli.Command) error {
	max := 20

	cmd.Walk(func(cmd *cli.Command) error {
		path := cmd.Path()
		command := path[len(path)-1]

		if len(path) == 1 || command == "help" || cmd.Hidden {
			return nil
		}

		indent := strings.Repeat("  ", len(path[1:])-1)
		space := strings.Repeat(" ", max-(len(command)+len(indent)))

		fmt.Printf("%s%s %s - %s\n", indent, command, space, cmd.Usage)

		return nil
	})

	return nil
}

Observed behavior

This prints this at the end:

[..]
version               - show esctl version information
debug                 - developer only
help-jsonpath         - show jsonpath help
  bash                - Output bash completion script
  zsh                 - Output zsh completion script
  fish                - Output fish completion script
  pwsh                - Output pwsh completion script

So, the command completion has been skipped, but its subcommands are not hidden and show up anyway.

Expected behavior

I'd like to have a way to check the parent of a cmd if it's hidden, eg:

if len(path) == 1 || command == "help" || cmd.Hidden || cmd.Parent.Hidden {
	return nil
}

Additional context

Complete code can be found here, to generate the tree, run: esctl --show-command-tree

Run go version and paste its output here

go version go1.25.8 linux/amd64

Run go env and paste its output here

AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/scip/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/scip/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3184483892=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/home/scip/dev/esctl/go.mod'
GOMODCACHE='/home/scip/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/scip/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/scip/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.8.linux-amd64'
GOSUMDB='sum.golang.org'
GOTELEMETRY='off'
GOTELEMETRYDIR='/home/scip/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/scip/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.8.linux-amd64/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.25.8'
GOWORK=''
PKG_CONFIG='pkg-config'

And many thanks for the cool Walk() and Path() feature, I'm loving it!

Activity

  1. added
    kind/bugdescribes or fixes a bug
    status/triagemaintainers still need to look into this
    area/v3relates to / is being considered for v3
    on Jun 23, 2026
  2. dearchap commented on Jun 28, 2026

    @dearchap
    Contributor

    @TLINDEN Here

    if len(path) == 1 || command == "help" || cmd.Hidden { return nil }

    for cmd.Hidden is your intent to check the current cmd in the fn or the root cmd that Walk is called on ?

  3. TLINDEN commented on Jun 29, 2026

    @TLINDEN
    Author

    Both, I want to know if the current command is hidden as well if its root (or root of the root etc, up top) is hidden.

    So, in the example of the completion command:

    root (visible)
        completion (hidden)
             zsh
             bash
             fish
    

    So, when the current command is "fish", I want to know if "completion" is hidden.

    Another idea is to make the Hidden inheritable. So, if a command is hidden, its subcommands might be as well, maybe according to a setting.

    Btw, my current workaround looks like this:

    func Tree(cmd *cli.Command) error {
    	max := 20
    
    	return WalkVisible(cmd, func(cmd *cli.Command) error {
    		path := cmd.Path()
    		command := path[len(path)-1]
    
    		if len(path) == 1 || command == "help" {
    			return nil
    		}
    
    		indent := strings.Repeat("  ", len(path[1:])-1)
    		space := strings.Repeat(" ", max-(len(command)+len(indent)))
    
    		fmt.Printf("%s%s %s - %s\n", indent, command, space, cmd.Usage)
    
    		return nil
    	})
    }
    
    func WalkVisible(cmd *cli.Command, fn func(*cli.Command) error) error {
    	if fn == nil {
    		return nil
    	}
    
    	if cmd.Hidden {
    		return nil
    	}
    
    	if err := fn(cmd); err != nil {
    		return err
    	}
    
    	for _, sub := range cmd.Commands {
    		if err := WalkVisible(sub, fn); err != nil {
    			return err
    		}
    	}
    
    	return nil
    }

    So I just finish the recursion if a command is hidden and ignore thereby any subcommands.

  4. added 2 commits that reference this issue on Jul 3, 2026
    6635fe3
    f759d84
  5. abitrolly commented on Jul 3, 2026

    @abitrolly
    Contributor

    This tree printer would be a good addon to https://pkg.go.dev/github.com/urfave/cli/v3#pkg-examples (just saying).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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