Skip to content

Migration: errorhandling.Fatal exits non-zero on special errors

Picked up via a routine dependency bump of gitlab.com/phpboyscout/go/errorhandling. No GTB API changed; the change is in observable process exit codes, so it is recorded here for any tool that shells out to itself or is driven from scripts.

What changed

Previously, when Check/Fatal handled a special error — ErrRunSubCommand (printed as usage) or ErrNotImplemented — it returned before the LevelFatal branch ran, so h.Exit was never called. A main that ended with handler.Fatal(err) on "a subcommand is required" printed usage and then exited 0, so if mytool …; then in a shell script treated an invalid invocation as success. (Cobra, by contrast, exits non-zero in the same situation.)

Fatal now still presents the special error and honours the fatal level with a non-zero exit. Usage/special errors exit with code 2 (the conventional Unix "command misuse" code, distinct from a generic 1), unless a specific code was set with WithExitCode.

Before:

$ mytool            # subcommand required
Usage: mytool [command]
...
$ echo $?
0                   # looked like success

After:

$ mytool
Usage: mytool [command]
...
$ echo $?
2                   # command misuse — distinct from generic failure

What you need to do

  • Usually nothing — this fixes a latent bug where a misinvocation reported success. Scripts using if mytool; then now correctly treat a usage error as a failure.
  • If a script relied on the old exit-0 behaviour for a "subcommand required" or "not implemented" path, update it to expect a non-zero (2) exit.
  • Non-fatal levels (Error, Warn) are unchanged and remain non-exiting.

The dead WithWriter option / Writer field was removed from go/errorhandling in the same release (it was a documented no-op — all output already flows through the logger and the usage seam). If you configured it, delete the call; it had no effect.