1 Error Handling
Virgil edited this page 2026-02-23 04:54:00 +00:00

Error Handling

Error Creation

// Simple error (replaces fmt.Errorf)
return cli.Err("invalid model: %s", name)

// Wrap with context (nil-safe)
return cli.Wrap(err, "load config")  // "load config: <original>"

// Wrap with i18n grammar
return cli.WrapVerb(err, "load", "config")    // "Failed to load config: <original>"
return cli.WrapAction(err, "connect")          // "Failed to connect: <original>"

Error Inspection

Re-exports of errors package for convenience:

if cli.Is(err, os.ErrNotExist) { ... }

var exitErr *cli.ExitError
if cli.As(err, &exitErr) {
    os.Exit(exitErr.Code)
}

combined := cli.Join(err1, err2, err3)

Exit Codes

// Return specific exit code from a command
return cli.Exit(2, fmt.Errorf("validation failed"))

The ExitError type is checked in Main() — commands that return *ExitError cause the process to exit with that code.

Fatal Functions (Deprecated)

These exist for legacy code but should not be used in new commands. Return errors from RunE instead.

// DON'T use these in new code:
cli.Fatal(err)                        // prints + os.Exit(1)
cli.Fatalf("bad: %v", err)           // prints + os.Exit(1)
cli.FatalWrap(err, "load config")    // prints + os.Exit(1)
cli.FatalWrapVerb(err, "load", "x")  // prints + os.Exit(1)

Pattern: Commands Return Errors

// GOOD: return error, let Main() handle exit
func runBuild(cmd *cli.Command, args []string) error {
    if err := compile(); err != nil {
        return cli.WrapVerb(err, "compile", "project")
    }
    cli.Success("Build complete")
    return nil
}

// BAD: calling os.Exit from library code
func runBuild(cmd *cli.Command, args []string) error {
    if err := compile(); err != nil {
        cli.Fatal(err) // DON'T DO THIS
    }
    return nil
}