temporalio/temporal · error
UpdateTaskQueue encountered ExpiryTime not set for sticky ta
Error message
UpdateTaskQueue encountered ExpiryTime not set for sticky task queue
What it means
UpdateTaskQueue panics when a sticky task queue update omits ExpiryTime, mirroring the CreateTaskQueue invariant. Sticky queues must remain expirable across their whole lifecycle; an update that clears or never sets ExpiryTime would make the sticky queue row effectively immortal, so the persistence layer fails fast with a panic.
Source
Thrown at common/persistence/task_manager.go:84
ExpiryTime: taskQueueInfo.ExpiryTime,
TaskQueueInfo: taskQueueInfoBlob,
}
if err := m.taskStore.CreateTaskQueue(ctx, internalRequest); err != nil {
return nil, err
}
return &CreateTaskQueueResponse{}, nil
}
func (m *taskManagerImpl) UpdateTaskQueue(
ctx context.Context,
request *UpdateTaskQueueRequest,
) (*UpdateTaskQueueResponse, error) {
taskQueueInfo := request.TaskQueueInfo
if taskQueueInfo.LastUpdateTime == nil {
panic("UpdateTaskQueue encountered LastUpdateTime not set")
}
if taskQueueInfo.ExpiryTime == nil && taskQueueInfo.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY {
panic("UpdateTaskQueue encountered ExpiryTime not set for sticky task queue")
}
taskQueueInfoBlob, err := m.serializer.TaskQueueInfoToBlob(taskQueueInfo)
if err != nil {
return nil, err
}
internalRequest := &InternalUpdateTaskQueueRequest{
NamespaceID: request.TaskQueueInfo.GetNamespaceId(),
TaskQueue: request.TaskQueueInfo.GetName(),
TaskType: request.TaskQueueInfo.GetTaskType(),
RangeID: request.RangeID,
TaskQueueInfo: taskQueueInfoBlob,
TaskQueueKind: request.TaskQueueInfo.GetKind(),
ExpiryTime: taskQueueInfo.ExpiryTime,
PrevRangeID: request.PrevRangeID,
}View on GitHub (pinned to bde624efd1)
Solutions
- Preserve or refresh ExpiryTime (time.Now().UTC().Add(stickyTTL)) whenever Kind is sticky on update.
- If the queue should no longer be sticky, update with Kind TASK_QUEUE_KIND_NORMAL so ExpiryTime is not required.
- When loading task queue info from storage for an update, re-validate sticky invariants before mutating.
- Add a validation helper applied to every UpdateTaskQueueRequest build site.
Example fix
// before
updateInfo := loadedInfo // Kind sticky, ExpiryTime lost in deserialization
_, err := taskManager.UpdateTaskQueue(ctx, &persistence.UpdateTaskQueueRequest{TaskQueueInfo: updateInfo})
// after
if updateInfo.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY && updateInfo.ExpiryTime == nil {
updateInfo.ExpiryTime = timestamp.TimePtr(time.Now().UTC().Add(stickyTaskQueueTTL))
}
_, err := taskManager.UpdateTaskQueue(ctx, &persistence.UpdateTaskQueueRequest{TaskQueueInfo: updateInfo}) Defensive patterns
Strategy: validation
Validate before calling
func validateTQUpdate(tq *persistencespb.TaskQueueInfo) error {
if tq.LastUpdateTime == nil { return errors.New("LastUpdateTime not set") }
if tq.ExpiryTime == nil && tq.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY {
return errors.New("sticky task queue requires ExpiryTime")
}
return nil
} Type guard
func stickyExpiryMissing(tq *persistencespb.TaskQueueInfo) bool { return tq.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY && tq.ExpiryTime == nil } Try / catch
// pre-validate; a panic here would take down the whole service process
if err := validateTQUpdate(tqInfo); err != nil { return nil, err }
_, err := mgr.UpdateTaskQueue(ctx, &persistence.UpdateTaskQueueRequest{TaskQueueInfo: tqInfo}) Prevention
- Refresh ExpiryTime on every sticky update rather than copying stale info
- Re-derive sticky invariants after any Kind change or blob deserialization
- Unit-test the full Create/Update lifecycle of sticky queues with expiry assertions
When it happens
Trigger: Calling PersistenceManager.UpdateTaskQueue with TaskQueueInfo.Kind == enumspb.TASK_QUEUE_KIND_STICKY and TaskQueueInfo.ExpiryTime == nil (LastUpdateTime must be non-nil or the earlier panic fires first).
Common situations: Reusing a TaskQueueInfo deserialized from an ES/DB blob where ExpiryTime was dropped; code that switches a task queue's Kind to sticky during update without refreshing expiry; test fixtures copying only Name and Kind fields.
Related errors
- CreateTaskQueue encountered ExpiryTime not set for sticky ta
- UpdateTaskQueue encountered LastUpdateTime not set
- unsupported partition kind:
- page size to read history tasks must be positive
- history task from queue has nil blob
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/748d07116128664d.
Report an issue: GitHub.