temporalio/temporal · critical
could not decode next time cache as proto or json
Error message
could not decode next time cache as proto or json
What it means
The scheduler workflow persists a 'next time' cache as a side-effect/memo payload. On replay it tries to decode the cached value as proto (NextTimeCache); if that fails it falls back to a legacy JSON encoding. If neither works, the workflow panics because continuing schedule computation without the cache would be incorrect. This is a determinism/replay-integrity guard.
Source
Thrown at service/worker/scheduler/workflow.go:649
cache.NominalTimes = cache.NominalTimes[0:len(cache.NextTimes)]
cache.NominalTimes = append(cache.NominalTimes, int64(next.Nominal.Sub(start)))
}
cache.NextTimes = append(cache.NextTimes, int64(next.Next.Sub(start)))
t = next.Next
}
return cache
})
// Previous versions of this workflow returned a json-encoded value here. This will attempt
// to unmarshal it into a proto struct. We want this to fail so we can convert it manually,
// but it might not. To be sure, check StartTime also (the field names differ between json
// and proto so json.Unmarshal will never fill in StartTime).
if val.Get(&s.nextTimeCacheV2) == nil && s.nextTimeCacheV2.GetStartTime() != nil {
return
}
// Try as json value
var jsonVal jsonNextTimeCacheV2
if val.Get(&jsonVal) != nil || jsonVal.Start.IsZero() {
panic("could not decode next time cache as proto or json")
}
s.nextTimeCacheV2 = &schedulespb.NextTimeCache{
Version: int64(jsonVal.Version),
StartTime: timestamppb.New(jsonVal.Start),
NextTimes: make([]int64, len(jsonVal.Results)),
NominalTimes: make([]int64, len(jsonVal.Results)),
Completed: jsonVal.Completed,
}
for i, res := range jsonVal.Results {
s.nextTimeCacheV2.NextTimes[i] = int64(res.Next.Sub(jsonVal.Start))
s.nextTimeCacheV2.NominalTimes[i] = int64(res.Nominal.Sub(jsonVal.Start))
}
}
func (s *scheduler) getNextTime(after time.Time) GetNextTimeResult {
// Implementation using a cache to save markers + computation.
if s.hasMinVersion(NewCacheAndJitter) {
return s.getNextTimeV2(after, after)View on GitHub (pinned to bde624efd1)
Solutions
- Upgrade/keep the scheduler worker version compatible with the version that wrote the cache so proto decoding succeeds
- Reset/terminate and restart the affected schedule workflows so the cache is recomputed (schedules will re-derive next times)
- Restore the payload from a backup or re-run side-effects from a point before the corruption
Defensive patterns
Strategy: fallback
Validate before calling
var raw []byte
if val.Get(&raw) == nil {
var probe schedulespb.NextTimeCache
var jprobe jsonNextTimeCacheV2
if proto.Unmarshal(raw, &probe) != nil && json.Unmarshal(raw, &jprobe) != nil {
// payload unreadable by both codecs: schedule will panic on replay
}
} Type guard
func cacheReadable(raw []byte) bool {
var p schedulespb.NextTimeCache
var j jsonNextTimeCacheV2
return proto.Unmarshal(raw, &p) == nil || (json.Unmarshal(raw, &j) == nil && !j.Start.IsZero())
} Prevention
- Never terminate/reset schedule workflows across major upgrades without checking worker compatibility
- Use workflow patching/versioning when changing the cache payload schema
- Keep scheduler workers the same version as the server during upgrades
- Back up workflow history before manual persistence surgery
When it happens
Trigger: fillNextTimeCacheV2 (service/worker/scheduler/workflow.go:649) reads a stored payload during workflow replay and both the proto decode and JSON decode fail — corrupted payload, a version written by an incompatible code path, or a value structurally not matching either schema (e.g. jsonVal.Start zero because fields were renamed).
Common situations: Upgrading Temporal across a scheduler payload format change with in-flight schedules; payload corruption from persistence issues; hand-editing or migrating workflow data between clusters without schema compatibility.
Related errors
- can't decode TweakablePolicies:
- out of range
- conflicting timezone names
- ${errs}
- CronString has time zone but missing fields
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/8eec6e79aa1856e4.
Report an issue: GitHub.