{"record":{"id":"268fdfac933eaf98","repo":"JuliusBrussee/caveman","slug":"ccr-recovery-handle-not-found","errorCode":null,"errorMessage":"ccr: recovery handle not found","messagePattern":"ccr: recovery handle not found","errorType":"exception","errorClass":"ErrNotFound","httpStatus":null,"severity":"error","filePath":"engine/ccr/store.go","lineNumber":29,"sourceCode":"// platforms (store_sqlite.go) and a pure-Go in-memory map under js/wasm\n// (store_wasm.go), since modernc.org/sqlite does not build for js/wasm. Both\n// expose the same type and methods, so the engine is unaware of the difference.\npackage ccr\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n)\n\n// ErrNotFound is returned by Get when a handle is unknown. The store never\n// guesses a recovery — an unknown handle is an explicit miss.\nvar ErrNotFound = errors.New(\"ccr: recovery handle not found\")\n\n// ErrBudgetExceeded means a new recovery was refused before publishing lossy\n// bytes because the local store's configured payload budget would be exceeded.\n// Existing handles remain intact and retrievable; callers must pass through.\nvar ErrBudgetExceeded = errors.New(\"ccr: storage budget exceeded\")\n\n// ObjectType is a closed typed-working-memory enum. Unknown values fail closed:\n// adapters may preserve unknown native payloads outside CCR, but may not invent\n// retrieval semantics for them.\ntype ObjectType string\n\nconst (\n\tObjectFileObservation      ObjectType = \"FileObservation\"\n\tObjectSearchResult         ObjectType = \"SearchResult\"\n\tObjectCommandResult        ObjectType = \"CommandResult\"\n\tObjectTestResult           ObjectType = \"TestResult\"\n\tObjectBuildResult          ObjectType = \"BuildResult\"\n\tObjectDiffSnapshot         ObjectType = \"DiffSnapshot\"","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/engine/ccr/store.go#L11-L47","documentation":"Returned by the CCR store's Get when the recovery handle passed in does not correspond to any stored recovery. The store is deliberately an explicit-miss design: it never guesses or substitutes a recovery for an unknown handle. Callers should treat this as a normal cache miss, not a corruption event.","triggerScenarios":"Calling store.Get(handle) with a handle that was never published, was garbage-collected/expired, came from a different session or store instance (e.g. after a DB file swap or restore), or was truncated/mistyped when forwarded between components.","commonSituations":"Recovery handles serialized into logs or task records and replayed against a fresh SQLite CCR store; handle passed across process restart where the in-memory (wasm) store lost state; typo or whitespace corruption of the handle string during transport.","solutions":["Verify the handle was originally returned by a successful Put/Publish on the same store instance","Check whether the store was recreated (new DB path, process restart with in-memory store) and republish the recovery","Handle the miss explicitly: skip the recovery path and continue without it rather than retrying the same handle","If handles cross process boundaries, persist them in the same durable store the recoveries live in"],"exampleFix":"// before\nrec, err := store.Get(handle)\nif err != nil {\n    return fmt.Errorf(\"get recovery: %w\", err) // fatal on miss\n}\n\n// after\nrec, err := store.Get(handle)\nif errors.Is(err, ccr.ErrNotFound) {\n    // explicit miss: degrade gracefully\n    return nil\n}\nif err != nil {\n    return fmt.Errorf(\"get recovery: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"rec, err := store.Get(handle)\nif errors.Is(err, ccr.ErrNotFound) {\n    // explicit miss: proceed without the recovery\n}\nif err != nil {\n    return fmt.Errorf(\"ccr get: %w\", err)\n}","preventionTips":["Persist handles in the same durable store as the recoveries so they never diverge","Treat handles as opaque: never reconstruct or trim them","Always branch on errors.Is(err, ccr.ErrNotFound) before generic error handling"],"tags":["ccr","go","recovery","cache-miss","sentinel-error"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}