affaan-m/ECC · warning · ErrNotFound
not found
Error message
not found
What it means
A Go sentinel error ErrNotFound = errors.New("not found") from the error-handling skill's domain package. It is wrapped into repository errors via fmt.Errorf("user %s: %w", id, ErrNotFound) and matched at the handler layer with errors.Is to produce an HTTP 404. Sentinels enable typed error handling across layers without exposing implementation detail.
Source
Thrown at skills/error-handling/SKILL.md:265
logger.exception("Unexpected error", exc_info=exc)
return JSONResponse(
status_code=500,
content={"error": {"code": "INTERNAL_ERROR", "message": "An unexpected error occurred"}},
)
```
## Go
### Sentinel Errors and Error Wrapping
```go
package domain
import "errors"
// Sentinel errors for type-checking
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrConflict = errors.New("conflict")
)
// Wrap errors with context — never lose the original
func (r *UserRepository) FindByID(ctx context.Context, id string) (*User, error) {
user, err := r.db.QueryRow(ctx, "SELECT * FROM users WHERE id = $1", id)
if errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
}
if err != nil {
return nil, fmt.Errorf("querying user %s: %w", id, err)
}
return user, nil
}
// At the handler level, unwrap to determine response
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {View on GitHub (pinned to 01e15490f0)
Solutions
- At the handler, switch on errors.Is(err, ErrNotFound) to return 404 instead of a generic 500.
- Distinguish 'not found' from 'gone' (soft-deleted) by adding a distinct sentinel.
- Ensure every repository consistently wraps sql.ErrNoRows as ErrNotFound so handlers can rely on it.
- Avoid leaking internal ids in the 404 message; return a generic 'resource not found' to the client.
Example fix
// before
return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
// after: also map to 404 at the boundary
user, err := h.service.GetUser(ctx, id)
if err != nil {
if errors.Is(err, domain.ErrNotFound) {
http.NotFound(w, r)
return
}
http.Error(w, "internal error", http.StatusInternalServerError)
return
} Defensive patterns
Strategy: validation
Validate before calling
// confirm existence before acting on a fetched resource
if exists, err := repo.Exists(ctx, id); err == nil && !exists {
return httpError(404, "not found")
} Type guard
null
Try / catch
null
Prevention
- Wrap sql.ErrNoRows as ErrNotFound consistently in every repository.
- At handlers, branch on errors.Is(err, ErrNotFound) -> 404.
- Validate id format first (400) so 404 means 'well-formed but absent'.
When it happens
Trigger: A repository query (FindByID, etc.) wraps ErrNotFound when the underlying driver returns sql.ErrNoRows (no row matched the WHERE clause). The handler then unwraps the chain and maps ErrNotFound to 404.
Common situations: Resource deleted between listing and fetch; id typo in a route parameter; tenant/multitenant scope filtering out the row; race between concurrent delete and read.
Related errors
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/e94190dafa09bd06.
Report an issue: GitHub.