affaan-m/ECC · error
get user %s: %w
Error message
get user %s: %w
What it means
Wrapped error from the GetUser example in golang-patterns. GetUser delegates to db.FindUser(id) and, on any non-nil return, wraps it as fmt.Errorf("get user %s: %w", id, err). This is shown as the 'clear and direct' style: a single early-return that preserves the cause via %w and adds the id as context. The message surfaces whenever the underlying lookup fails for any reason.
Source
Thrown at skills/golang-patterns/SKILL.md:30
## When to Activate
- Writing new Go code
- Reviewing Go code
- Refactoring existing Go code
- Designing Go packages/modules
## Core Principles
### 1. Simplicity and Clarity
Go favors simplicity over cleverness. Code should be obvious and easy to read.
```go
// Good: Clear and direct
func GetUser(id string) (*User, error) {
user, err := db.FindUser(id)
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
// Bad: Overly clever
func GetUser(id string) (*User, error) {
return func() (*User, error) {
if u, e := db.FindUser(id); e == nil {
return u, nil
} else {
return nil, e
}
}()
}
```
### 2. Make the Zero Value Useful
View on GitHub (pinned to 01e15490f0)
Solutions
- Inspect with errors.Is against the store's sentinel errors (e.g. ErrNotFound) to decide between 404 and 500.
- Validate the id (non-empty, correct shape) before calling FindUser to surface input errors earlier.
- Confirm the data layer connectivity and that FindUser's contract still matches caller assumptions.
- Add structured logging of the wrapped cause at the boundary so users see a generic message but operators see detail.
Example fix
// before
user, err := GetUser(id)
if err != nil {
return err
}
// after
user, err := GetUser(id)
switch {
case errors.Is(err, ErrNotFound):
return NotFoundResponse(id)
case err != nil:
log.Printf("get user %s failed: %v", id, err)
return InternalErrorResponse()
} Defensive patterns
Strategy: try-catch
Validate before calling
func validateUserID(id string) error {
if strings.TrimSpace(id) == "" {
return errors.New("id required")
}
return nil
} Type guard
// Distinguish 'not found' from 'transient failure' for the caller.
func isNotFound(err error) bool {
return errors.Is(err, ErrNotFound) || errors.Is(err, sql.ErrNoRows)
} Try / catch
user, err := GetUser(id)
switch {
case isNotFound(err):
return NotFoundResponse(id)
case err != nil:
log.Printf("get user %s: %v", id, err)
return InternalErrorResponse()
} Prevention
- Define and reuse sentinel errors in the store layer so callers can errors.Is.
- Validate ids at the handler boundary before calling the service.
- Never swallow the wrapped cause; always %w it upward.
- Document which errors FindUser can return.
When it happens
Trigger: Calling GetUser(id) when db.FindUser returns a non-nil error. This includes not-found, validation of the id, transport errors to the store, or any failure inside FindUser that is propagated up rather than swallowed.
Common situations: Repository returns a sentinel not-found; id format is invalid and the store rejects it; the data layer is temporarily unavailable; a refactor changed FindUser's error semantics and the caller now bubbles an unexpected cause.
Related errors
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/5329c7488e745b0e.
Report an issue: GitHub.