Install
openclaw skills install go-error-handlerAnalyze Go error handling patterns — detect swallowed errors, missing error wrapping, sentinel vs custom error usage, type assertions, and idiomatic error pr...
openclaw skills install go-error-handlerDeep analysis of error handling patterns in Go codebases. Detects swallowed errors, missing wrapping context, incorrect sentinel usage, unsafe type assertions, and non-idiomatic patterns. Produces prioritized findings aligned with Go proverbs and stdlib conventions.
Use when: reviewing Go code for production readiness, auditing error handling, or establishing team conventions.
cat go.mod 2>/dev/null | head -10
find . -name "*.go" -not -path '*/vendor/*' | wc -l
find . -type d -name "errors" -o -name "errs" -not -path '*/vendor/*' 2>/dev/null
grep "pkg/errors\|go.uber.org/multierr\|cockroachdb/errors" go.mod 2>/dev/null
Determine: Go version (affects errors.Is/As availability), error library usage, custom error infrastructure.
Most critical check. A swallowed error is an err return value that is ignored.
# Explicit underscore ignoring error
grep -rn '\b_\s*=.*(' --include="*.go" . 2>/dev/null | grep -v 'vendor/\|_test.go' | head -25
# err assigned but never checked
grep -rn 'err\s*=' --include="*.go" . 2>/dev/null | grep -v 'if.*err\|return.*err\|vendor/' | head -20
# defer with ignored error (defer f.Close())
grep -rn 'defer.*Close()\|defer.*Flush()\|defer.*Rollback()' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -15
# http.Error without return (handler continues after error response)
grep -A2 'http\.Error(' --include="*.go" . 2>/dev/null | grep -v 'return\|vendor/' | head -15
Severity guide:
_ = someFunc() on functions that can fail meaningfullydefer f.Close() without error handling — use named return + defer closurefmt.Fprintf to stdout (acceptable in CLIs)# Proper wrapping with %w (Go 1.13+)
grep -rn 'fmt\.Errorf.*%w' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -15
# %v/%s instead of %w (loses error chain)
grep -rn 'fmt\.Errorf.*%[vs].*err\b' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -15
# Bare return of error without wrapping (loses context)
grep -rn 'return.*err$' --include="*.go" . 2>/dev/null | grep -v 'nil\|vendor/\|_test.go' | head -20
# Error message conventions (should not start with capital or end with punctuation)
grep -rn 'fmt\.Errorf("[ ]*[A-Z]' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10
grep -rn 'errors\.New(".*\.")' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10
Flag:
fmt.Errorf("failed: %v", err) breaks errors.Is()/errors.As() — use %wreturn err across package boundaries loses call-site context — wrap with fmt.Errorf("operation: %w", err)# Sentinel errors
grep -rn 'var\s\+Err[A-Z].*=\s*errors\.New' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -15
# String comparison instead of errors.Is (fragile)
grep -rn 'err\.Error()\s*==' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10
# Direct equality (breaks with wrapping)
grep -rn 'err\s*==\s*Err\|err\s*!=\s*Err' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10
# errors.Is / errors.As usage (correct patterns)
grep -rn 'errors\.Is(\|errors\.As(' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -15
# Custom error types
grep -rn 'func.*Error()\s*string' --include="*.go" . 2>/dev/null | grep -v 'vendor/\|_test.go' | head -15
# Type assertion on errors (fragile with wrapping)
grep -rn 'err\.(\*' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10
Flag:
err.Error() == "not found" — use errors.Is(err, ErrNotFound)err == ErrNotFound fails if wrapped — use errors.Is()err.(*MyError) fails on wrapped errors — use errors.As()Unwrap() errorerrors.New("not found") in multiple places should be a sentinel# Internal errors leaked to client (security risk)
grep -rn 'http\.Error(w,.*err\.Error()' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10
# Panic in non-test code
grep -rn 'panic(' --include="*.go" . 2>/dev/null | grep -v 'vendor/\|_test.go' | head -10
# Goroutines without error propagation
grep -B2 -A10 'go func()' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -30
# errgroup usage
grep -rn 'errgroup\|g\.Go\|group\.Go' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10
Flag:
http.Error(w, err.Error(), 500) sends raw errors to clients — log internally, return generic messageerrgroup.WithContext to cancel remaining goroutines on first errorgrep -rn 'wantErr\|expectErr\|shouldErr' --include="*_test.go" . 2>/dev/null | grep -v 'vendor/' | head -15
# Packages with no error path tests
for pkg in $(find . -name "*.go" -not -name "*_test.go" -not -path '*/vendor/*' | sed 's|/[^/]*$||' | sort -u); do
if [ -z "$(grep -rl 'wantErr\|shouldErr' ${pkg}/*_test.go 2>/dev/null)" ]; then
echo "NO_ERROR_TESTS: $pkg"
fi
done | head -10
# Go Error Handling Analysis — [Module Name]
## Summary
- Files: N | Go: 1.XX | Error lib: stdlib/pkg/errors
- Critical: N | Warnings: N | Error test coverage: low/moderate/good
## Critical Findings
### [C1] Swallowed Database Error
- **File**: internal/repo/user.go:45
- **Code**: `rows, _ := db.Query(query, args...)`
- **Fix**: Handle error, return `fmt.Errorf("query users: %w", err)`
### [C2] Internal Error Leaked to Client
- **File**: internal/handler/order.go:78
- **Fix**: Log error, return `http.Error(w, "internal server error", 500)`
## Error Wrapping Issues
| File | Line | Issue | Fix |
|------|------|-------|-----|
| repo/user.go | 23 | `%v` not `%w` | Change to `%w` |
| service/order.go | 45 | Bare `return err` | Add context wrapping |
## Sentinel Error Inventory
| Package | Sentinel | Checked With |
|---------|----------|-------------|
| repo | ErrNotFound | errors.Is (correct) |
| auth | ErrExpired | err == (fix: errors.Is) |
## Recommendations
1. Replace N instances of `_ = operation()` with error handling
2. Change N `%v` to `%w` in fmt.Errorf calls
3. Add wrapping context to N bare `return err` statements
4. Install recovery middleware for HTTP server
5. Add error path tests to N packages
| Rule | Severity | Go Proverb |
|---|---|---|
| Never ignore errors | Critical | "Errors are values" |
| Wrap with context at boundaries | High | Each layer adds context |
| Use %w not %v | High | Preserve error chains |
| Use errors.Is not == | High | Works across wrapping |
| Use errors.As not type assertion | High | Works across wrapping |
| Return after http.Error | Critical | Prevent double-write |
| Don't leak internals to clients | Critical | Security boundary |
| Propagate errors from goroutines | High | Silent failures are worst |
errcheck ./... to catch unchecked errors mechanicallygolangci-lint with wrapcheck, errcheck, goerr113 linterserrors.Join (Go 1.20+) or go.uber.org/multierrdefer Close() errors for writers — flushed data may fail to write