4.9 KiB
Cobra Commands, Hooks, and Args Validators
The Run* lifecycle
Cobra commands have five run hooks. Cobra executes them in this fixed order:
PersistentPreRunE → PreRunE → RunE → PostRunE → PersistentPostRunE
Each *E hook returns error. The non-*E variants (PersistentPreRun, PreRun, Run, PostRun, PersistentPostRun) have signature func(cmd *cobra.Command, args []string) — they cannot signal failure without os.Exit or panic. Always use the *E variants.
Which hook to use
| Hook | Scope | When to use |
|---|---|---|
PersistentPreRunE |
Parent + all descendants | Config init, auth check, telemetry setup — must run before every subcommand |
PreRunE |
This command only | Validation that runs only for this command before RunE |
RunE |
This command only | Main handler — the primary business logic |
PostRunE |
This command only | Cleanup that runs only if RunE succeeded |
PersistentPostRunE |
Parent + all descendants | Global cleanup (close connections, flush buffers) |
Inheritance rules
PersistentPreRunE defined on the root command runs before every subcommand. But if a child command defines its own PersistentPreRunE, it replaces (does not chain) the parent's hook. Call the parent explicitly if you need both:
var childCmd = &cobra.Command{
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// call parent's hook first
if err := rootCmd.PersistentPreRunE(cmd, args); err != nil {
return err
}
// child-specific logic
return nil
},
}
Execution stops on first error
If PersistentPreRunE returns an error, cobra stops — RunE and later hooks never run. Use this for fail-fast auth checks.
Args validators
Args validators run before RunE. Cobra prints a clear error message and exits without calling RunE when validation fails.
Built-in validators
cobra.NoArgs // fails if any positional args provided
cobra.ArbitraryArgs // accepts any number of args (default)
cobra.ExactArgs(n int) // requires exactly n args
cobra.MinimumNArgs(n int) // requires at least n args
cobra.MaximumNArgs(n int) // requires at most n args
cobra.RangeArgs(min, max int) // requires between min and max args
cobra.OnlyValidArgs // all args must be in ValidArgs list
cobra.ExactValidArgs(n int) // exactly n args, all in ValidArgs
Composing validators with MatchAll
var deleteCmd = &cobra.Command{
Use: "delete <resource>",
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
ValidArgs: []string{"pod", "service", "deployment"},
RunE: func(cmd *cobra.Command, args []string) error {
return doDelete(args[0])
},
}
Custom validators
Signature: func(cmd *cobra.Command, args []string) error
func validatePositiveInt(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("requires exactly 1 arg, got %d", len(args))
}
n, err := strconv.Atoi(args[0])
if err != nil || n <= 0 {
return fmt.Errorf("argument must be a positive integer, got %q", args[0])
}
return nil
}
var cmd = &cobra.Command{
Args: validatePositiveInt,
RunE: func(cmd *cobra.Command, args []string) error { /* ... */ },
}
Combine custom validators with built-in ones using MatchAll:
Args: cobra.MatchAll(cobra.MinimumNArgs(1), validateAllPositive),
Command registration and ordering
func init() {
// groups must be registered before AddCommand
rootCmd.AddGroup(&cobra.Group{ID: "core", Title: "Core Commands:"})
rootCmd.AddGroup(&cobra.Group{ID: "management", Title: "Management Commands:"})
serveCmd.GroupID = "core"
migrateCmd.GroupID = "management"
rootCmd.AddCommand(serveCmd, migrateCmd, versionCmd)
}
versionCmd has no GroupID — it appears in the default section.
Annotations
Cobra supports arbitrary command annotations for framework-level metadata:
var serveCmd = &cobra.Command{
Annotations: map[string]string{
"category": "network",
"requires-auth": "true",
},
}
// read in a middleware hook:
if serveCmd.Annotations["requires-auth"] == "true" {
// enforce auth
}
Hidden and deprecated commands
var internalCmd = &cobra.Command{
Hidden: true, // not shown in help, still executable
}
var oldCmd = &cobra.Command{
Deprecated: "use `newcmd` instead", // shown in help, prints warning on use
}
cobra.CheckErr
cobra.CheckErr(err) is a convenience function: if err != nil, it prints the error to cmd.ErrOrStderr() and calls os.Exit(1). Use it only in main() where you want a hard exit — not inside RunE where returning the error is preferred.
func main() {
cobra.CheckErr(rootCmd.Execute())
}