Agent Skills: Go Error Handling

Go error handling patterns including wrapping, custom error types, errors.Is/As, and error conventions. Use when handling, creating, or checking errors in Go.

UncategorizedID: jovermier/cc-stack-marketplace/go-error-handling

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jovermier/cc-stack-marketplace/tree/HEAD/plugins/cc-go/skills/go-error-handling

Skill Files

Browse the full folder contents for go-error-handling.

Download Skill

Loading file tree…

plugins/cc-go/skills/go-error-handling/SKILL.md

Skill Metadata

Name
go-error-handling
Description
Go error handling patterns including wrapping, custom error types, errors.Is/As, and error conventions. Use when handling, creating, or checking errors in Go.

Go Error Handling

Expert guidance for proper error handling in Go.

Quick Reference

| Operation | Pattern | Example | |-----------|---------|---------| | Wrap with context | fmt.Errorf with %w | fmt.Errorf("opening file: %w", err) | | Create custom error | struct with Error() | type ValidationError struct {...} | | Check error type | errors.Is | errors.Is(err, ErrNotFound) | | Extract error | errors.As | errors.As(err, &validationErr) | | Sentinel errors | var at package level | var ErrNotFound = errors.New("not found") | | Ignore errors | Never | Always check err != nil |

What Do You Need?

  1. Error wrapping - Adding context to errors
  2. Custom error types - Creating structured errors
  3. Error inspection - errors.Is, errors.As
  4. Sentinel errors - Package-level error values
  5. Error conventions - When to wrap, return, or create

Specify a number or describe your error handling scenario.

Routing

| Response | Reference to Read | |----------|-------------------| | 1, "wrap", "context", "fmt.Errorf" | wrapping.md | | 2, "custom", "type", "struct" | custom-errors.md | | 3, "check", "errors.Is", "errors.As" | inspection.md | | 4, "sentinel", "package", "global" | sentinel.md | | 5, general error handling | Read relevant references |

Critical Rules

  • Never ignore errors: Always check err != nil
  • Wrap with %w: Use %w to preserve error type for errors.Is
  • Wrap at boundaries: Wrap when crossing package boundaries
  • Don't wrap twice: Avoid double-wrapping the same error
  • Use errors.Is for sentinel: Check if error is a specific value
  • Use errors.As for types: Extract and inspect custom error types

Error Handling Patterns

Wrapping Errors

// Good: Wrap with context using %w
func (s *Service) Process(id string) error {
    item, err := s.repo.Find(id)
    if err != nil {
        return fmt.Errorf("finding item %s: %w", id, err)
    }
    // ...
}

// Bad: Wrapping with %v loses error type
return fmt.Errorf("finding item %s: %v", id, err)  // Can't use errors.Is()

// Bad: Double wrapping
return fmt.Errorf("processing: %w", fmt.Errorf("finding: %w", err))

Custom Error Types

// Define custom error type
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed for field %s: %s", e.Field, e.Message)
}

// Return custom error
func (s *Service) Validate(input Input) error {
    if input.Email == "" {
        return &ValidationError{
            Field:   "email",
            Message: "is required",
        }
    }
    return nil
}

Error Inspection

// Check for sentinel error
if errors.Is(err, ErrNotFound) {
    // Handle not found
}

// Extract and check custom error type
var validationErr *ValidationError
if errors.As(err, &validationErr) {
    // Access validationErr.Field, validationErr.Message
}

// Check multiple possibilities
if errors.Is(err, ErrNotFound) || errors.Is(err, ErrAccessDenied) {
    // Handle both cases
}

Sentinel Errors

// Package-level sentinel errors
var (
    ErrNotFound    = errors.New("not found")
    ErrAccessDenied = errors.New("access denied")
    ErrInvalidInput = errors.New("invalid input")
)

// Use in returns
func (r *Repository) Find(id string) (*Item, error) {
    // ...
    return nil, ErrNotFound
}

// Check in callers
if err != nil {
    if errors.Is(err, ErrNotFound) {
        return nil, nil  // Not found is not an error here
    }
    return nil, err  // Other errors are still errors
}

When to Wrap vs Return

| Scenario | Action | |----------|--------| | Crossing package boundary | Wrap with context | | Internal function | Return as-is | | Adding retry logic | Don't wrap (check with errors.Is) | | Adding logging | Log then wrap or return | | API layer | Wrap for user-friendly messages |

Common Mistakes

| Mistake | Severity | Fix | |---------|----------|-----| | Ignoring errors | Critical | Always check err != nil | | Using %v instead of %w | High | Use %w to preserve error type | | Double wrapping | Medium | Wrap only at boundary | | Panicking on errors | Critical | Return errors, don't panic | | Creating strings for errors | Low | Use errors.New() or sentinel | | Wrapping nil error | Medium | Check err != nil before wrapping |

Reference Index

| File | Topics | |------|--------| | wrapping.md | fmt.Errorf with %w, when to wrap | | custom-errors.md | Error types, methods, best practices | | inspection.md | errors.Is, errors.As, type switches | | sentinel.md | Package-level errors, comparison |

Success Criteria

Error handling is correct when:

  • No errors are ignored (all checked)
  • Errors wrapped at package boundaries with %w
  • Custom error types for domain-specific errors
  • Sentinel errors for expected conditions
  • errors.Is used for sentinel checking
  • errors.As used for type inspection
  • No panic on errors (except in package init/main)