Go error handling patterns including wrapping, custom error types, errors.Is/As, and error conventions. Use when handling, creating, or checking errors in Go.
This Claude skill provides expert guidance on Go error handling patterns, including error wrapping with %w, implementing custom error types, and utilizing the errors.Is and errors.As functions for robust error inspection. It helps developers follow Go idiomatic conventions to ensure code reliability, maintainability, and effective debugging by preserving error context across package boundaries.
Use Cases
Implementing structured error handling in Go microservices to preserve original error types while adding execution context.
Designing custom error types to provide detailed, domain-specific validation feedback in API responses.
Refactoring legacy Go codebases to replace simple string-based error checks with modern errors.Is and errors.As patterns.
Conducting code reviews to ensure consistent error wrapping practices and prevent common mistakes like ignoring errors or double-wrapping.
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?
Error wrapping - Adding context to errors
Custom error types - Creating structured errors
Error inspection - errors.Is, errors.As
Sentinel errors - Package-level error values
Error conventions - When to wrap, return, or create
Specify a number or describe your error handling scenario.
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
}