Loading...
Loading...
Go error handling patterns: wrapping with context, sentinel errors, custom error types, errors.Is/As chains, and HTTP error mapping. Use when implementing error returns, defining package-level errors, creating custom error types, wrapping errors with fmt.Errorf, or checking errors with errors.Is/As. Use for "error handling", "fmt.Errorf", "errors.Is", "errors.As", "sentinel error", "custom error", or "%w". Do NOT use for general Go development, debugging runtime panics, or logging strategy.
npx skill4agent add notque/claude-code-toolkit go-error-handling_ = fn()return err%wfmt.Errorferrors.Iserrors.Asgo_symbol_referencesgo_diagnosticsgopls referencesreturn err%wscripts/check-errors.shreturn errbash scripts/check-errors.sh --helpfmt.Errorf%werrors.Newerrorerrors.Iserrors.As| Situation | Pattern | Example |
|---|---|---|
| Operation failed, caller does not check type | Wrap with context | |
| Caller needs to check for specific condition | Sentinel error | |
| Caller needs structured error data | Custom error type | |
| Error at HTTP boundary | Status mapping | |
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("load config from %s: %w", path, err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config JSON from %s: %w", path, err)
}
return &cfg, nil
}
// Output: "parse config JSON from /etc/app.json: invalid character...""load config""error loading config"%wpackage mypackage
import "errors"
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
func GetUser(ctx context.Context, id string) (*User, error) {
user, err := db.QueryUser(ctx, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
return nil, fmt.Errorf("query user %s: %w", id, err)
}
return user, nil
}
// Caller:
user, err := GetUser(ctx, id)
if errors.Is(err, ErrNotFound) {
// handle not found
}type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on field %q: %s", e.Field, e.Message)
}
func ValidateUser(u *User) error {
if u.Email == "" {
return &ValidationError{Field: "email", Message: "required"}
}
return nil
}
// Caller uses errors.As:
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("Field %s failed: %s\n", valErr.Field, valErr.Message)
}if errors.Is(err, ErrNotFound) { /* handle */ }
if errors.Is(err, context.Canceled) { /* cancelled */ }
if errors.Is(err, os.ErrNotExist) { /* file missing */ }var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Printf("Path: %s, Op: %s\n", pathErr.Path, pathErr.Op)
}
var netErr net.Error
if errors.As(err, &netErr) {
if netErr.Timeout() { /* handle timeout */ }
}func errorToStatus(err error) int {
switch {
case errors.Is(err, ErrNotFound):
return http.StatusNotFound
case errors.Is(err, ErrUnauthorized):
return http.StatusUnauthorized
case errors.Is(err, ErrInvalidInput):
return http.StatusBadRequest
default:
return http.StatusInternalServerError
}
}func TestProcessUser(t *testing.T) {
tests := []struct {
name string
input *User
wantErr error
}{
{
name: "nil user",
input: nil,
wantErr: ErrInvalidInput,
},
{
name: "missing email",
input: &User{Name: "test"},
wantErr: ErrInvalidInput,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ProcessUser(tt.input)
if tt.wantErr != nil {
if !errors.Is(err, tt.wantErr) {
t.Errorf("got error %v, want %v", err, tt.wantErr)
}
} else if err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}%werrors.Iserrors.As%v%wfmt.Errorffmt.Errorf%wvarUnwrap()errors.Newvar ErrXerrors.Newreturn fmt.Errorf("error: %w", err)return fmt.Errorf("failed: %w", err)return fmt.Errorf("load user %s from database: %w", userID, err)return errreturn fmt.Errorf("outer operation: %w", err)file.Close()data, _ := fetchData()if err := file.Close(); err != nil { log.Printf("close file: %v", err) }_ = file.Close()if strings.Contains(err.Error(), "not found")errors.Is(err, ErrNotFound)errors.As(err, &target)return fmt.Errorf("process: error processing: %w", fmt.Errorf("processing failed: %w", err))return fmt.Errorf("process user request: %w", err)| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "Simple error, no need to wrap" | Unwrapped errors lose context at every level | Always wrap with |
| "String check is fine for now" | String matching breaks when messages change | Use |
| "No one will check this error" | You cannot predict caller needs | Define sentinel if it crosses a package boundary |
| "Custom type is overkill" | Evaluate the actual need, but do not skip if callers need structured data | Match pattern to situation (Step 1 table) |
${CLAUDE_SKILL_DIR}/references/patterns.md