error-handling-go — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited error-handling-go (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Handle errors in Go the idiomatic way: explicit checking, structured wrapping, and type-safe error inspection for reliable, debuggable programs.
Add context at each call layer so the final error tells the full story.
func GetUser(ctx context.Context, id string) (*User, error) {
row := db.QueryRowContext(ctx, "SELECT name, email FROM users WHERE id = $1", id)
var user User
if err := row.Scan(&user.Name, &user.Email); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("user %s not found: %w", id, ErrNotFound)
}
return nil, fmt.Errorf("querying user %s: %w", id, err)
}
return &user, nil
}
func HandleGetUser(w http.ResponseWriter, r *http.Request) {
user, err := GetUser(r.Context(), r.PathValue("id"))
if err != nil {
if errors.Is(err, ErrNotFound) {
http.Error(w, "user not found", http.StatusNotFound)
return
}
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(user)
}Define package-level errors for known, expected failure conditions.
package user
import "errors"
var (
ErrNotFound = errors.New("user not found")
ErrAlreadyExists = errors.New("user already exists")
ErrInvalidEmail = errors.New("invalid email address")
)
func Create(ctx context.Context, email string) (*User, error) {
if !isValidEmail(email) {
return nil, ErrInvalidEmail
}
existing, err := findByEmail(ctx, email)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, fmt.Errorf("checking existing user: %w", err)
}
if existing != nil {
return nil, fmt.Errorf("email %s: %w", email, ErrAlreadyExists)
}
// ... create user
return &User{Email: email}, nil
}Use struct errors when you need to carry additional context (codes, fields, metadata).
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
type NotFoundError struct {
Resource string
ID string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s with id %s not found", e.Resource, e.ID)
}
// Check with errors.As
func handleError(err error) {
var notFound *NotFoundError
if errors.As(err, ¬Found) {
fmt.Printf("Could not find %s: %s\n", notFound.Resource, notFound.ID)
return
}
var validationErr *ValidationError
if errors.As(err, &validationErr) {
fmt.Printf("Bad input for %s: %s\n", validationErr.Field, validationErr.Message)
return
}
fmt.Printf("Unexpected error: %v\n", err)
}errors.Is checks the error chain for a specific value (sentinel). errors.As checks the error chain for a specific type (struct error).
// errors.Is -- value comparison through the wrap chain
func processOrder(ctx context.Context, id string) error {
order, err := getOrder(ctx, id)
if err != nil {
// Works even if err was wrapped multiple times
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("order %s: %w", id, ErrNotFound)
}
return fmt.Errorf("fetching order %s: %w", id, err)
}
_ = order
return nil
}
// errors.As -- type extraction through the wrap chain
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
http.Error(w, "internal error", 500)
}
}()
next.ServeHTTP(w, r)
})
}Use errgroup to manage errors from multiple goroutines cleanly.
import "golang.org/x/sync/errgroup"
func fetchAll(ctx context.Context, urls []string) ([]Response, error) {
g, ctx := errgroup.WithContext(ctx)
responses := make([]Response, len(urls))
for i, url := range urls {
g.Go(func() error {
resp, err := fetch(ctx, url)
if err != nil {
return fmt.Errorf("fetching %s: %w", url, err)
}
responses[i] = resp
return nil
})
}
if err := g.Wait(); err != nil {
return nil, fmt.Errorf("fetching urls: %w", err)
}
return responses, nil
}Collect multiple errors when you need to report all failures, not just the first.
func validate(u User) error {
var errs []error
if u.Name == "" {
errs = append(errs, &ValidationError{Field: "name", Message: "required"})
}
if u.Email == "" {
errs = append(errs, &ValidationError{Field: "email", Message: "required"})
}
if u.Age < 0 || u.Age > 150 {
errs = append(errs, &ValidationError{Field: "age", Message: "out of range"})
}
return errors.Join(errs...)
}
// Checking joined errors
err := validate(user)
if err != nil {
var ve *ValidationError
if errors.As(err, &ve) {
// Gets the first matching ValidationError in the joined set
fmt.Println(ve.Field, ve.Message)
}
}as intentionally ignored.
// BAD
result, _ := riskyOperation()
// GOOD
result, err := riskyOperation()
if err != nil { return fmt.Errorf("risky op: %w", err) }%v instead of %w breaks the error chain.errors.Is and errors.As will not find the original error.
Logging at every layer creates duplicate, noisy log entries.
// BAD
if err != nil {
log.Printf("failed: %v", err) // logged here
return err // AND propagated -- will be logged again
}panic for truly unrecoverable situations(programmer bugs, impossible states). Return errors for anything a caller can handle.
| Tool | Purpose |
|---|---|
fmt.Errorf("context: %w", err) | Wrap with context |
errors.New("message") | Create sentinel error |
errors.Is(err, target) | Check value in chain |
errors.As(err, &target) | Extract type from chain |
errors.Join(errs...) | Combine multiple errors |
errgroup.Group | Concurrent error collection |
Wrapping rule: add context about WHAT was being done, not WHY it failed (the wrapped error already says why).
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.