JuliusBrussee/caveman · error · ErrNotFound
ccr: recovery handle not found
Error message
ccr: recovery handle not found
What it means
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.
Source
Thrown at engine/ccr/store.go:29
// platforms (store_sqlite.go) and a pure-Go in-memory map under js/wasm
// (store_wasm.go), since modernc.org/sqlite does not build for js/wasm. Both
// expose the same type and methods, so the engine is unaware of the difference.
package ccr
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"slices"
"strings"
"time"
)
// ErrNotFound is returned by Get when a handle is unknown. The store never
// guesses a recovery — an unknown handle is an explicit miss.
var ErrNotFound = errors.New("ccr: recovery handle not found")
// ErrBudgetExceeded means a new recovery was refused before publishing lossy
// bytes because the local store's configured payload budget would be exceeded.
// Existing handles remain intact and retrievable; callers must pass through.
var ErrBudgetExceeded = errors.New("ccr: storage budget exceeded")
// ObjectType is a closed typed-working-memory enum. Unknown values fail closed:
// adapters may preserve unknown native payloads outside CCR, but may not invent
// retrieval semantics for them.
type ObjectType string
const (
ObjectFileObservation ObjectType = "FileObservation"
ObjectSearchResult ObjectType = "SearchResult"
ObjectCommandResult ObjectType = "CommandResult"
ObjectTestResult ObjectType = "TestResult"
ObjectBuildResult ObjectType = "BuildResult"
ObjectDiffSnapshot ObjectType = "DiffSnapshot"View on GitHub (pinned to 27d5a3981a)
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
Example fix
// before
rec, err := store.Get(handle)
if err != nil {
return fmt.Errorf("get recovery: %w", err) // fatal on miss
}
// after
rec, err := store.Get(handle)
if errors.Is(err, ccr.ErrNotFound) {
// explicit miss: degrade gracefully
return nil
}
if err != nil {
return fmt.Errorf("get recovery: %w", err)
} Defensive patterns
Strategy: try-catch
Try / catch
rec, err := store.Get(handle)
if errors.Is(err, ccr.ErrNotFound) {
// explicit miss: proceed without the recovery
}
if err != nil {
return fmt.Errorf("ccr get: %w", err)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ccr: storage budget exceeded
- ccr: typed object session_id is required
- ccr: typed object content_hash does not match data
- ccr: typed object byte lengths cannot be negative
- ccr: typed object id collision
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/268fdfac933eaf98.
Report an issue: GitHub.