JuliusBrussee/caveman · error
cacheengine: invalid epoch
Error message
cacheengine: invalid epoch
What it means
Returned by validatePlanRequest when validIdentity(request.Epoch, 4096, false) fails. Epoch is a required identity string (non-empty, <=4096 bytes, structurally valid) that names the prefix-generation an epoch belongs to; it keys the frozen prefix state that StartEpoch installs and Plan reasons about.
Source
Thrown at cacheengine/engine.go:273
return Plan{
Decision: DecisionNewEpoch,
Reason: string(cacheguard.DecisionNewEpoch),
ProfileID: profile.ID,
Mode: profile.Mode,
Attribution: profile.Attribution,
PrefixSHA256: result.PrefixSHA256,
EconomicsBasis: economicsBasis,
KeyShardCount: 1,
Warnings: warnings,
}, nil
}
func validatePlanRequest(request PlanRequest) error {
if !validIdentity(request.Scope, 4096, false) {
return errors.New("cacheengine: invalid scope")
}
if !validIdentity(request.Epoch, 4096, false) {
return errors.New("cacheengine: invalid epoch")
}
if !validIdentity(request.PartitionKey, 4096, true) {
return errors.New("cacheengine: invalid partition key")
}
if request.ExpectedCalls < 0 || request.ExpectedRequestsPerMinute < 0 {
return errors.New("cacheengine: negative traffic expectation")
}
profile := normalizedProfile(request.Profile)
if !validIdentity(profile.ID, 256, false) || !validIdentity(profile.Provider, 64, true) || !validIdentity(profile.OptimizerID, 256, true) {
return errors.New("cacheengine: invalid profile identity")
}
if profile.Mode != ModeUnsupported && profile.Mode != ModeImplicit && profile.Mode != ModeAffinity && profile.Mode != ModeExplicit {
return fmt.Errorf("cacheengine: unknown mode %q", profile.Mode)
}
if profile.Mode != ModeUnsupported {
if profile.MaxBreakpoints <= 0 || profile.MinPrefixTokens < 0 || profile.MaxRPMPerKey < 0 || profile.TTL < 0 {
return errors.New("cacheengine: invalid cache thresholds")
}View on GitHub (pinned to 27d5a3981a)
Solutions
- Always populate PlanRequest.Epoch with a non-empty stable identifier, e.g. 'v1' or a content hash of the prefix
- Bump the epoch deliberately whenever the stable prefix content changes, so caches re-freeze
- Keep it under 4096 bytes and within the identity character rules
Example fix
// before
eng.StartEpoch(cacheengine.PlanRequest{Scope: "s"}) // Epoch empty
// after
eng.StartEpoch(cacheengine.PlanRequest{Scope: "s", Epoch: "prefix-sha256:9f2c..."}) Defensive patterns
Strategy: validation
Validate before calling
if strings.TrimSpace(req.Epoch) == "" || len(req.Epoch) > 4096 {
return errors.New("epoch must be a non-empty identifier within 4096 bytes")
} Try / catch
if _, err := eng.StartEpoch(req); err != nil {
if err.Error() == "cacheengine: invalid epoch" {
return fmt.Errorf("epoch %q invalid; set a stable generation id", req.Epoch)
}
return Plan{}, err
} Prevention
- Derive the epoch from a digest of the stable prefix so it changes exactly when content changes
- Fail fast on empty epoch in your request builder — the zero-value string always trips this
When it happens
Trigger: Calling Plan or StartEpoch with an empty Epoch, an Epoch longer than 4096 bytes, or one containing characters rejected by the identity rules.
Common situations: Using an incrementing counter that was never initialized (zero value string is empty); epoch derived from a timestamp plus long context that overflows the limit; migration code that renamed fields and stopped populating Epoch.
Related errors
- cacheengine: invalid scope
- token budget must be positive, zero (default), or UnlimitedT
- cachebench: no providers
- cachebench: invalid trace read limits
- cache-replay: -trace and positive request/token/trace/respon
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/d5085941b72d3c5d.
Report an issue: GitHub.