JuliusBrussee/caveman · error
ccr: unknown lifecycle
Error message
ccr: unknown lifecycle %q
What it means
prepareObject (engine/ccr/store.go:122) validates the Object.Lifecycle field before persisting a CCR object via putObject/PutObject. The store only accepts the four enumerated lifecycle values: Hot, Warm, Cold, and LifecycleArchived. Empty strings are defaulted to Hot, but any other non-empty value is rejected with this error. This is a fail-closed enum guard ensuring tiering metadata in the SQLite recovery store is always well-defined.
Solutions
- Set Lifecycle to one of the exported constants: Hot, Warm, Cold, or LifecycleArchived (or leave it empty to default to Hot).
- Normalize the incoming value before calling PutObject — trim whitespace and match case exactly against the constants.
- If migrating data from another enum vocabulary, map old values to the four supported lifecycle constants before storage.
- Check for typos or custom values (e.g. "archive" instead of LifecycleArchived) in the code path that builds the Object.
Example fix
// before
obj := ccr.Object{Type: ccr.ObjectEvidenceBundle, SessionID: "s1", Lifecycle: ccr.Lifecycle("hot")}
store.PutObject(ctx, obj) // error: ccr: unknown lifecycle "hot"
// after
obj := ccr.Object{Type: ccr.ObjectEvidenceBundle, SessionID: "s1", Lifecycle: ccr.Hot}
store.PutObject(ctx, obj) Defensive patterns
Strategy: validation
Validate before calling
func validLifecycle(l ccr.Lifecycle) bool {
return l == "" || l == ccr.Hot || l == ccr.Warm || l == ccr.Cold || l == ccr.LifecycleArchived
}
if !validLifecycle(obj.Lifecycle) {
return fmt.Errorf("refusing to store object with lifecycle %q", obj.Lifecycle)
} Type guard
func isValidLifecycle(v ccr.Lifecycle) bool {
switch v {
case "", ccr.Hot, ccr.Warm, ccr.Cold, ccr.LifecycleArchived:
return true
}
return false
} Try / catch
obj, err := store.PutObject(ctx, o)
if err != nil {
var unknown *fmt.Errorf
if strings.Contains(err.Error(), "unknown lifecycle") {
log.Fatalf("invalid lifecycle %q: use Hot|Warm|Cold|LifecycleArchived", o.Lifecycle)
}
return err
} Prevention
- Always construct Lifecycle values from the exported constants, never string literals.
- Omit Lifecycle entirely when you want the Hot default.
- Add a compile-time or unit-test assertion enumerating all valid lifecycle constants.
- Validate lifecycle values at config-load time, before they reach the store.
When it happens
Trigger: Calling PutObject (directly or via putObject) with obj.Lifecycle set to a value that is not exactly Hot, Warm, Cold, or LifecycleArchived — e.g. a hand-rolled string cast like Lifecycle("hot"), Lifecycle("archive"), a typo such as Lifecycle("Hott"), or a lifecycle value imported from another subsystem's vocabulary.
Common situations: Developers constructing Object structs manually instead of through store helpers, migrating records whose lifecycle was persisted with an older or different enum spelling, or deserializing lifecycle from config/JSON where case differs ("Hot" vs "hot"). Also occurs when a custom tier value from a fork or newer version is written into an older store build.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- arm must be or
- assembly slot has unknown stability
- assembly slot has unknown stability
- bedrock endpoint must be runtime or mantle
- cave_ccr_budget_exceeded
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/66d6362fd9355b0a.
Report an issue: GitHub.
Appendix: source
Thrown at engine/ccr/store.go:122
func prepareObject(obj Object) (Object, error) {
if _, ok := objectTypes[obj.Type]; !ok {
return Object{}, fmt.Errorf("ccr: unknown object type %q", obj.Type)
}
if strings.TrimSpace(obj.SessionID) == "" {
return Object{}, errors.New("ccr: typed object session_id is required")
}
if obj.Currentness == "" {
obj.Currentness = Current
}
if obj.Currentness != Current && obj.Currentness != Stale && obj.Currentness != Archived {
return Object{}, fmt.Errorf("ccr: unknown currentness %q", obj.Currentness)
}
if obj.Lifecycle == "" {
obj.Lifecycle = Hot
}
if obj.Lifecycle != Hot && obj.Lifecycle != Warm && obj.Lifecycle != Cold && obj.Lifecycle != LifecycleArchived {
return Object{}, fmt.Errorf("ccr: unknown lifecycle %q", obj.Lifecycle)
}
if obj.CreatedAt.IsZero() {
obj.CreatedAt = time.Now().UTC()
} else {
obj.CreatedAt = obj.CreatedAt.UTC()
}
dataSum := sha256.Sum256(obj.Data)
computedHash := "sha256:" + hex.EncodeToString(dataSum[:])
if obj.ContentHash == "" {
obj.ContentHash = computedHash
} else if obj.ContentHash != computedHash {
return Object{}, errors.New("ccr: typed object content_hash does not match data")
}
if obj.OriginalByteLength == 0 {
obj.OriginalByteLength = len(obj.Data)
}
if obj.StoredByteLength == 0 {
obj.StoredByteLength = len(obj.Data)View on GitHub (pinned to 3ee70a1026)