SilenceUsage and SilenceErrors on the root command
byob-errors.4
errors
Problem: the single most common cobra papercut. You return a runtime error
from RunE — "connection refused", "file not found", "quota exceeded" —
and cobra prints the entire usage blob (description, flags, subcommands)
followed by the error message. Users hate this: the signal is drowned in
boilerplate, and the usage blob is pointless because the problem isn't
that they misinvoked the command.
Idea: set SilenceUsage = true and SilenceErrors = true on the root
command. Both settings cascade to every child via inheritance.
SilenceUsage stops the usage dump on every cobra-emitted error;
SilenceErrors stops cobra from printing the error string at all — which
you then do yourself in the top-level runner, so you control the
formatting (color, hints, exit code mapping).
The catch: SilenceUsage = true cascades to flag-parsing errors too —
"unknown flag", "missing argument", and the unknown-command path no
longer print usage. The runner's "error: ..." line is the only signal
the user sees. Pair this with a cobra.SetFlagErrorFunc that wraps
pflag errors as *FlagError (so the runner maps them to exit 2) and a
string-prefix check for "unknown command" in the runner (cobra has no
typed sentinel for that path). The exit code carries the "you invoked
me wrong" semantics; the message carries the diagnostic.
If you'd rather have usage on flag errors and silence only on RunE
errors, do the gh-cli inversion: drop SilenceUsage from root, and set
cmd.SilenceUsage = true as the first line of every RunE. Cobra
emits flag errors before RunE, so usage still prints there. The
trade-off is a per-command line of boilerplate the runner-on-root
approach avoids.
Tradeoffs: SilenceErrors = true means you're responsible for printing
errors yourself. That's what your top-level runner already does anyway if
you're following the semantic-error-types + ErrHint patterns — those
patterns assume you own the output.
When not to use: never. If you're following the rest of this library, the combination is strictly better than cobra's defaults.
Design
func NewCmdRoot(f *Factory) *cobra.Command {
root := &cobra.Command{
Use: "mytool",
Short: "do the thing",
// Don't dump usage on runtime errors; users already know how to invoke it.
SilenceUsage: true,
// Don't print errors; the top-level runner formats them.
SilenceErrors: true,
}
// Wrap pflag's flag-parse errors so the runner exits 2 per byob-errors.1.
root.SetFlagErrorFunc(func(c *cobra.Command, err error) error {
return &cmdutil.FlagError{Err: err}
})
// ... register groups and subcommands ...
return root
}
// runner: wrap cobra's untyped "unknown command" message as FlagError
// before mapping. Cobra has no public sentinel for that path.
func classify(err error) error {
if err == nil { return nil }
var fe *cmdutil.FlagError
if errors.As(err, &fe) { return err }
if strings.HasPrefix(err.Error(), "unknown command ") {
return &cmdutil.FlagError{Err: err}
}
return err
}
// main.go
func main() {
root := pkgcmd.NewCmdRoot(factory.New())
err := root.ExecuteContext(ctx)
os.Exit(runner.MapErrorToExitCode(classify(err)))
}