{"record":{"id":"e94190dafa09bd06","repo":"affaan-m/ECC","slug":"not-found","errorCode":null,"errorMessage":"not found","messagePattern":"not found","errorType":"error_code","errorClass":"ErrNotFound","httpStatus":null,"severity":"warning","filePath":"skills/error-handling/SKILL.md","lineNumber":265,"sourceCode":"    logger.exception(\"Unexpected error\", exc_info=exc)\n    return JSONResponse(\n        status_code=500,\n        content={\"error\": {\"code\": \"INTERNAL_ERROR\", \"message\": \"An unexpected error occurred\"}},\n    )\n```\n\n## Go\n\n### Sentinel Errors and Error Wrapping\n\n```go\npackage domain\n\nimport \"errors\"\n\n// Sentinel errors for type-checking\nvar (\n    ErrNotFound    = errors.New(\"not found\")\n    ErrUnauthorized = errors.New(\"unauthorized\")\n    ErrConflict     = errors.New(\"conflict\")\n)\n\n// Wrap errors with context — never lose the original\nfunc (r *UserRepository) FindByID(ctx context.Context, id string) (*User, error) {\n    user, err := r.db.QueryRow(ctx, \"SELECT * FROM users WHERE id = $1\", id)\n    if errors.Is(err, sql.ErrNoRows) {\n        return nil, fmt.Errorf(\"user %s: %w\", id, ErrNotFound)\n    }\n    if err != nil {\n        return nil, fmt.Errorf(\"querying user %s: %w\", id, err)\n    }\n    return user, nil\n}\n\n// At the handler level, unwrap to determine response\nfunc (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/skills/error-handling/SKILL.md#L247-L283","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nreturn nil, fmt.Errorf(\"user %s: %w\", id, ErrNotFound)\n\n// after: also map to 404 at the boundary\nuser, err := h.service.GetUser(ctx, id)\nif err != nil {\n    if errors.Is(err, domain.ErrNotFound) {\n        http.NotFound(w, r)\n        return\n    }\n    http.Error(w, \"internal error\", http.StatusInternalServerError)\n    return\n}","handlingStrategy":"validation","validationCode":"// confirm existence before acting on a fetched resource\nif exists, err := repo.Exists(ctx, id); err == nil && !exists {\n    return httpError(404, \"not found\")\n}","typeGuard":"null","tryCatchPattern":"null","preventionTips":["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'."],"tags":["go","sentinel-error","repository","not-found"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}