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

  1. At the handler, switch on errors.Is(err, ErrNotFound) to return 404 instead of a generic 500.
  2. Distinguish 'not found' from 'gone' (soft-deleted) by adding a distinct sentinel.
  3. Ensure every repository consistently wraps sql.ErrNoRows as ErrNotFound so handlers can rely on it.
  4. 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

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.