All skills
cxuu avatar

/go-error-handling

@91f0c2e
by Charles Xucxuu/golang-skills165 stars
19

Use when writing Go code that returns, wraps, or handles errors — choosing between sentinel errors, custom types, and fmt.Errorf (%w vs %v), structuring error flow, or deciding whether to log or return. Also use when propagating errors across package boundaries or using errors.Is/As, even if the user doesn't ask about error strategy. Does not cover panic/recover patterns (see go-defensive).

Use this Skill: https://skilld.dev/gh/cxuu/golang-skills/go-error-handling

This session only. Nothing lands on disk.

referencesERROR-FLOW.md

≈1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Error Flow Patterns

Detailed patterns for error flow, the handle-once principle, and logging decisions.

Indent Error Flow

Handle errors before proceeding with normal code. This improves readability by enabling the reader to find the normal path quickly.

// Good: Error handling first, normal code unindented
if err != nil {
    // error handling
    return // or continue, etc.
}
// normal code
// Bad: Normal code hidden in else clause
if err != nil {
    // error handling
} else {
    // normal code that looks abnormal due to indentation
}

Avoid If-with-Initializer for Long-Lived Variables

If you use a variable for more than a few lines, move the declaration out:

// Good: Declaration separate from error check
x, err := f()
if err != nil {
    return err
}
// lots of code that uses x
// across multiple lines
// Bad: Variable scoped to else block, hard to read
if x, err := f(); err != nil {
    return err
} else {
    // lots of code that uses x
    // across multiple lines
}

Handle Errors Once

When a caller receives an error, it should handle each error only once. Choose ONE response:

  1. Return the error (wrapped or verbatim) for the caller to handle
  2. Log and degrade gracefully (don't return the error)
  3. Match and handle specific error cases, return others

If you return an error, don't log it yourself — let the caller handle it. Logging and returning the same error is the most common "handle errors once" violation, causing duplicate noise as callers up the stack also handle the error.

// Bad: Logs AND returns - causes noise in logs
u, err := getUser(id)
if err != nil {
    log.Printf("Could not get user %q: %v", id, err)
    return err  // Callers will also log this!
}

// Good: Wrap and return - let caller decide how to handle
u, err := getUser(id)
if err != nil {
    return fmt.Errorf("get user %q: %w", id, err)
}

// Good: Log and degrade gracefully (don't return error)
if err := emitMetrics(); err != nil {
    // Failure to write metrics should not break the application
    log.Printf("Could not emit metrics: %v", err)
}
// Continue execution...

// Good: Match specific errors, return others
tz, err := getUserTimeZone(id)
if err != nil {
    if errors.Is(err, ErrUserNotFound) {
        // User doesn't exist. Use UTC.
        tz = time.UTC
    } else {
        return fmt.Errorf("get user %q: %w", id, err)
    }
}

Logging vs Returning Errors

Handle an error exactly once — either log it or return it, never both.

Decision Flow

Error encountered?
├─ Can the caller act on it? → Return the error (with context via %w)
├─ Is this the top of the call chain? → Log and handle (return HTTP status, exit, etc.)
└─ Neither? → Log at appropriate level and continue

Don't Log and Return

// Bad: error is logged AND returned — appears twice in logs
func process(ctx context.Context, id string) error {
    result, err := fetch(ctx, id)
    if err != nil {
        log.Printf("failed to fetch %s: %v", id, err)
        return fmt.Errorf("fetching %s: %w", id, err)
    }
    return handle(result)
}

// Good: return with context — let the caller decide whether to log
func process(ctx context.Context, id string) error {
    result, err := fetch(ctx, id)
    if err != nil {
        return fmt.Errorf("fetching %s: %w", id, err)
    }
    return handle(result)
}

Structured Logging

Prefer structured logging (slog in Go 1.21+, or log/slog-compatible libraries) over log.Printf for production code:

// Good: structured fields are machine-parseable
slog.Error("fetch failed", "id", id, "err", err)

// Avoid: unstructured string interpolation
log.Printf("fetch failed for %s: %v", id, err)

Verbosity Levels

Level Use for
Error Actionable failures that need attention
Warn Degraded behavior that doesn't require immediate action
Info Key lifecycle events (startup, shutdown, config loaded)
Debug Diagnostic detail useful during development

Source: SKILL.md on GitHub

No alerts17d5 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    The skill is safe to use. It provides guidelines and a localized utility for verifying Go error handling conventions through AST analysis without any network activity or external dependencies.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer6mo

    1/5 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 91f0c2e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 3 months ago
What it can do
Runs commands
All 1 allowed tools
Bash(bash:*)

README badge

README badge for cxuu/golang-skills/go-error-handling