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
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)
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: go-error-handling-33description: Go error handling patterns including wrapping, custom error types, errors.Is/As, and error conventions. Use when handling, creating, or checking errors in Go. Use when this capability is needed.4---56# Go Error Handling78Expert guidance for proper error handling in Go.910## Quick Reference1112| Operation | Pattern | Example |13|-----------|---------|---------|14| Wrap with context | fmt.Errorf with %w | `fmt.Errorf("opening file: %w", err)` |15| Create custom error | struct with Error() | type ValidationError struct {...} |16| Check error type | errors.Is | `errors.Is(err, ErrNotFound)` |17| Extract error | errors.As | `errors.As(err, &validationErr)` |18| Sentinel errors | var at package level | `var ErrNotFound = errors.New("not found")` |19| Ignore errors | Never | Always check err != nil |2021## What Do You Need?22231. **Error wrapping** - Adding context to errors242. **Custom error types** - Creating structured errors253. **Error inspection** - errors.Is, errors.As264. **Sentinel errors** - Package-level error values275. **Error conventions** - When to wrap, return, or create2829Specify a number or describe your error handling scenario.3031## Routing3233| Response | Reference to Read |34|----------|-------------------|35| 1, "wrap", "context", "fmt.Errorf" | [wrapping.md](./references/wrapping.md) |36| 2, "custom", "type", "struct" | [custom-errors.md](./references/custom-errors.md) |37| 3, "check", "errors.Is", "errors.As" | [inspection.md](./references/inspection.md) |38| 4, "sentinel", "package", "global" | [sentinel.md](./references/sentinel.md) |39| 5, general error handling | Read relevant references |4041## Critical Rules4243- **Never ignore errors**: Always check err != nil44- **Wrap with %w**: Use %w to preserve error type for errors.Is45- **Wrap at boundaries**: Wrap when crossing package boundaries46- **Don't wrap twice**: Avoid double-wrapping the same error47- **Use errors.Is for sentinel**: Check if error is a specific value48- **Use errors.As for types**: Extract and inspect custom error types4950## Error Handling Patterns5152### Wrapping Errors53```go54// Good: Wrap with context using %w55func (s *Service) Process(id string) error {56 item, err := s.repo.Find(id)57 if err != nil {58 return fmt.Errorf("finding item %s: %w", id, err)59 }60 // ...61}6263// Bad: Wrapping with %v loses error type64return fmt.Errorf("finding item %s: %v", id, err) // Can't use errors.Is()6566// Bad: Double wrapping67return fmt.Errorf("processing: %w", fmt.Errorf("finding: %w", err))68```6970### Custom Error Types71```go72// Define custom error type73type ValidationError struct {74 Field string75 Message string76}7778func (e *ValidationError) Error() string {79 return fmt.Sprintf("validation failed for field %s: %s", e.Field, e.Message)80}8182// Return custom error83func (s *Service) Validate(input Input) error {84 if input.Email == "" {85 return &ValidationError{86 Field: "email",87 Message: "is required",88 }89 }90 return nil91}92```9394### Error Inspection95```go96// Check for sentinel error97if errors.Is(err, ErrNotFound) {98 // Handle not found99}100101// Extract and check custom error type102var validationErr *ValidationError103if errors.As(err, &validationErr) {104 // Access validationErr.Field, validationErr.Message105}106107// Check multiple possibilities108if errors.Is(err, ErrNotFound) || errors.Is(err, ErrAccessDenied) {109 // Handle both cases110}111```112113### Sentinel Errors114```go115// Package-level sentinel errors116var (117 ErrNotFound = errors.New("not found")118 ErrAccessDenied = errors.New("access denied")119 ErrInvalidInput = errors.New("invalid input")120)121122// Use in returns123func (r *Repository) Find(id string) (*Item, error) {124 // ...125 return nil, ErrNotFound126}127128// Check in callers129if err != nil {130 if errors.Is(err, ErrNotFound) {131 return nil, nil // Not found is not an error here132 }133 return nil, err // Other errors are still errors134}135```136137## When to Wrap vs Return138139| Scenario | Action |140|----------|--------|141| Crossing package boundary | Wrap with context |142| Internal function | Return as-is |143| Adding retry logic | Don't wrap (check with errors.Is) |144| Adding logging | Log then wrap or return |145| API layer | Wrap for user-friendly messages |146147## Common Mistakes148149| Mistake | Severity | Fix |150|---------|----------|-----|151| Ignoring errors | Critical | Always check err != nil |152| Using %v instead of %w | High | Use %w to preserve error type |153| Double wrapping | Medium | Wrap only at boundary |154| Panicking on errors | Critical | Return errors, don't panic |155| Creating strings for errors | Low | Use errors.New() or sentinel |156| Wrapping nil error | Medium | Check err != nil before wrapping |157158## Reference Index159160| File | Topics |161|------|--------|162| [wrapping.md](./references/wrapping.md) | fmt.Errorf with %w, when to wrap |163| [custom-errors.md](./references/custom-errors.md) | Error types, methods, best practices |164| [inspection.md](./references/inspection.md) | errors.Is, errors.As, type switches |165| [sentinel.md](./references/sentinel.md) | Package-level errors, comparison |166167## Success Criteria168169Error handling is correct when:170- No errors are ignored (all checked)171- Errors wrapped at package boundaries with %w172- Custom error types for domain-specific errors173- Sentinel errors for expected conditions174- errors.Is used for sentinel checking175- errors.As used for type inspection176- No panic on errors (except in package init/main)177178---179> Converted and distributed by [TomeVault](https://tomevault.io/claim/jovermier) — claim your Tome and manage your conversions.180<!-- tomevault:4.0:skill_md:2026-04-13 -->