From go-toolkit
Implements idiomatic Go error handling: wrapping with %w, sentinel errors via errors.Is, custom types with errors.As, and propagation patterns. Use for error returns and checks.
How this skill is triggered — by the user, by Claude, or both
Slash command
/go-toolkit:error-handlingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Implement idiomatic Go error handling patterns.
Implement idiomatic Go error handling patterns.
Basic error handling:
result, err := doSomething()
if err != nil {
return fmt.Errorf("do something: %w", err)
}
Sentinel error:
var ErrNotFound = errors.New("not found")
if errors.Is(err, ErrNotFound) {
// Handle not found
}
Basic error return:
func ReadFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read file %s: %w", path, err)
}
return data, nil
}
Multiple return values:
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
Using fmt.Errorf with %w:
func ProcessFile(path string) error {
data, err := ReadFile(path)
if err != nil {
return fmt.Errorf("process file: %w", err)
}
if err := Validate(data); err != nil {
return fmt.Errorf("validate data: %w", err)
}
return nil
}
Error chain:
// Original error
err := os.Open("file.txt")
// Wrapped once
err = fmt.Errorf("open config: %w", err)
// Wrapped again
err = fmt.Errorf("initialize app: %w", err)
// Unwrap to check original
if errors.Is(err, os.ErrNotExist) {
// Handle file not found
}
Define sentinel errors:
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
func GetUser(id string) (*User, error) {
user, ok := cache[id]
if !ok {
return nil, ErrNotFound
}
return user, nil
}
Check with errors.Is:
user, err := GetUser("123")
if errors.Is(err, ErrNotFound) {
// Handle not found case
return nil
}
if err != nil {
// Handle other errors
return err
}
Error type with fields:
type ValidationError struct {
Field string
Value interface{}
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed for %s: %s", e.Field, e.Msg)
}
func Validate(user *User) error {
if user.Email == "" {
return &ValidationError{
Field: "email",
Value: user.Email,
Msg: "email is required",
}
}
return nil
}
Check with errors.As:
if err := Validate(user); err != nil {
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("Field %s failed: %s\n", valErr.Field, valErr.Msg)
}
return err
}
Check immediately:
// Good
result, err := doSomething()
if err != nil {
return err
}
// Bad - don't defer error checking
result, err := doSomething()
// ... more code ...
if err != nil {
return err
}
Don't ignore errors:
// Bad
doSomething()
// Good
if err := doSomething(); err != nil {
log.Printf("warning: %v", err)
}
// Or explicitly ignore
_ = doSomething()
func LoadConfig() (*Config, error) {
data, err := readConfigFile()
if err != nil {
return nil, fmt.Errorf("load config: %w", err)
}
config, err := parseConfig(data)
if err != nil {
return nil, fmt.Errorf("load config: %w", err)
}
return config, nil
}
func ProcessBatch(items []Item) ([]Result, error) {
results := make([]Result, 0, len(items))
for _, item := range items {
result, err := process(item)
if err != nil {
return nil, fmt.Errorf("process item %s: %w", item.ID, err)
}
results = append(results, result)
}
return results, nil
}
type MultiError []error
func (m MultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
func ValidateAll(users []*User) error {
var errs MultiError
for _, user := range users {
if err := Validate(user); err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return errs
}
return nil
}
Use panic for programmer errors:
func MustCompile(pattern string) *regexp.Regexp {
re, err := regexp.Compile(pattern)
if err != nil {
panic(err) // Invalid pattern is programmer error
}
return re
}
Recover from panics:
func SafeExecute(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
fn()
return nil
}
type contextError struct {
op string
err error
}
func (e *contextError) Error() string {
return fmt.Sprintf("%s: %v", e.op, e.err)
}
func (e *contextError) Unwrap() error {
return e.err
}
func withContext(op string, err error) error {
if err == nil {
return nil
}
return &contextError{op: op, err: err}
}
func handler(w http.ResponseWriter, r *http.Request) {
user, err := getUser(r.Context(), r.URL.Query().Get("id"))
if errors.Is(err, ErrNotFound) {
http.Error(w, "User not found", http.StatusNotFound)
return
}
if errors.Is(err, ErrUnauthorized) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if err != nil {
log.Printf("get user: %v", err)
http.Error(w, "Internal error", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(user)
}
func processAsync(items []Item) error {
errCh := make(chan error, len(items))
for _, item := range items {
go func(it Item) {
errCh <- process(it)
}(item)
}
// Collect errors
for range items {
if err := <-errCh; err != nil {
return fmt.Errorf("process failed: %w", err)
}
}
return nil
}
Don't:
Error not matching with errors.Is:
Can't extract error type:
Lost error context:
npx claudepluginhub p/armanzeroeight-go-toolkit-plugins-go-toolkitProvides Go error handling patterns: basic errors, wrapping with %w, sentinel errors, custom types, errors.Is, and Unwrap for robust apps.
Go error handling patterns, wrapping, sentinel errors, custom error types, and the errors package. Grounded in Effective Go, Go Code Review Comments, and production-proven idioms. Use when implementing error handling, designing error types, debugging error chains, or reviewing error handling patterns. Trigger examples: "handle errors", "error wrapping", "custom error type", "sentinel errors", "errors.Is", "errors.As". Do NOT use for panic/recover patterns in middleware (use go-api-design) or test assertion errors (use go-test-quality).
Guides Go error handling strategy: sentinel vs typed vs opaque errors, wrapping with %w vs %v, and when to log vs return. Invoked automatically when working with Go errors.