Go error handling patterns including wrapping, custom error types, errors.Is/As, and error conventions. Use when handling, creating, or checking errors in Go.
Resources
1Install
npx skillscat add jovermier/cc-stack-marketplace/go-error-handling Install via the SkillsCat registry.
SKILL.md
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.
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)