PersistentPreRunE on root for app-wide middleware
byob-command-shape.5
command-shape
Problem: cross-cutting setup — logging configuration, authentication
handshake, config load, telemetry init — needs to happen before every
subcommand's business logic. Copy-pasting that setup into each RunE
or into every NewCmdXxx is duplicative and drifts.
Idea: put setup in PersistentPreRunE on the root command. Cobra's
execution order is
OnInitialize → root.PersistentPreRunE → cmd.PreRunE → cmd.RunE → cmd.PostRunE → root.PersistentPostRunE,
so anything on the root's PersistentPreRunE runs once per invocation,
before any subcommand's logic, with the parsed flags already bound. Use
it as application-wide middleware: load config, wire auth, bind stuff
onto the Factory, set up graceful-shutdown context handlers.
Skip for help / version / completion subcommands by checking
cmd.Name() or a command annotation — you don't want mytool --help
to call your auth server.
Tradeoffs: the root's PersistentPreRunE runs for every subcommand,
including ones that might not need its setup. Keep it fast, or gate it
behind a check. Alternative: per-subcommand PreRunE, but then you're
back to duplication.
When not to use: tiny single-command tools where setup is obvious and
lives in main(). The pattern pays off at 3+ subcommands that all need
the same preamble.
Design
func NewCmdRoot(f *Factory) *cobra.Command {
root := &cobra.Command{
Use: "mytool",
SilenceUsage: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// Skip for meta-commands that shouldn't trigger real setup.
switch cmd.Name() {
case "help", "completion", "version":
return nil
}
// Load config (still lazy under the hood; first access wins).
cfg, err := f.Config()
if err != nil { return err }
// Wire logging level from config.
setupLogging(f.IOStreams.ErrOut, cfg.LogLevel)
// Anything else every command needs: auth, telemetry, etc.
return nil
},
}
// add children...
return root
}