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

  1. Verify the handle was originally returned by a successful Put/Publish on the same store instance
  2. Check whether the store was recreated (new DB path, process restart with in-memory store) and republish the recovery
  3. Handle the miss explicitly: skip the recovery path and continue without it rather than retrying the same handle
  4. 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

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


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/268fdfac933eaf98. Report an issue: GitHub.