temporalio/temporal · error
CreateTaskQueue encountered ExpiryTime not set for sticky ta
Error message
CreateTaskQueue encountered ExpiryTime not set for sticky task queue
What it means
CreateTaskQueue in the persistence task manager panics when a sticky task queue is created without an ExpiryTime. Sticky task queues are ephemeral (they point at a specific workflow worker's cache) and the persistence layer relies on ExpiryTime to garbage-collect them; a missing ExpiryTime for TASK_QUEUE_KIND_STICKY is treated as a caller invariant violation, so the manager fails fast with panic instead of writing a row that could never expire.
Source
Thrown at common/persistence/task_manager.go:53
func (m *taskManagerImpl) Close() {
m.taskStore.Close()
}
func (m *taskManagerImpl) GetName() string {
return m.taskStore.GetName()
}
func (m *taskManagerImpl) CreateTaskQueue(
ctx context.Context,
request *CreateTaskQueueRequest,
) (*CreateTaskQueueResponse, error) {
taskQueueInfo := request.TaskQueueInfo
if taskQueueInfo.LastUpdateTime == nil {
panic("CreateTaskQueue encountered LastUpdateTime not set")
}
if taskQueueInfo.ExpiryTime == nil && taskQueueInfo.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY {
panic("CreateTaskQueue encountered ExpiryTime not set for sticky task queue")
}
taskQueueInfoBlob, err := m.serializer.TaskQueueInfoToBlob(taskQueueInfo)
if err != nil {
return nil, err
}
internalRequest := &InternalCreateTaskQueueRequest{
NamespaceID: request.TaskQueueInfo.GetNamespaceId(),
TaskQueue: request.TaskQueueInfo.GetName(),
TaskType: request.TaskQueueInfo.GetTaskType(),
TaskQueueKind: request.TaskQueueInfo.GetKind(),
RangeID: request.RangeID,
ExpiryTime: taskQueueInfo.ExpiryTime,
TaskQueueInfo: taskQueueInfoBlob,
}
if err := m.taskStore.CreateTaskQueue(ctx, internalRequest); err != nil {
return nil, err
}View on GitHub (pinned to bde624efd1)
Solutions
- Set TaskQueueInfo.ExpiryTime to a concrete timestamp (typically now + sticky task queue TTL, e.g. time.Now().Add(stickyTTL).UTC()) whenever Kind is TASK_QUEUE_KIND_STICKY.
- Derive the expiry from the client's StickyTaskQueueScheduleToStartTimeout / worker sticky settings rather than leaving it nil.
- If the queue is genuinely not sticky, verify Kind is left as TASK_QUEUE_KIND_NORMAL so the ExpiryTime check is bypassed.
- Add a unit test asserting ExpiryTime != nil for sticky kinds before invoking the persistence layer.
Example fix
// before
tqInfo := &persistencespb.TaskQueueInfo{
Kind: enumspb.TASK_QUEUE_KIND_STICKY,
LastUpdateTime: timestamp.TimePtr(time.Now().UTC()),
}
// after
tqInfo := &persistencespb.TaskQueueInfo{
Kind: enumspb.TASK_QUEUE_KIND_STICKY,
LastUpdateTime: timestamp.TimePtr(time.Now().UTC()),
ExpiryTime: timestamp.TimePtr(time.Now().UTC().Add(stickyTaskQueueTTL)),
} Defensive patterns
Strategy: validation
Validate before calling
func validateStickyTQ(tq *persistencespb.TaskQueueInfo) error {
if tq.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY && tq.ExpiryTime == nil {
return fmt.Errorf("sticky task queue %q requires ExpiryTime", tq.GetName())
}
return nil
} Type guard
func isSticky(tq *persistencespb.TaskQueueInfo) bool { return tq.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY } Try / catch
// Go panics are not catchable as errors without recover; validate before calling:
if err := validateStickyTQ(tqInfo); err != nil { return nil, err }
_, err := mgr.CreateTaskQueue(ctx, req) Prevention
- Centralize task queue info construction in one helper that always sets ExpiryTime for sticky kind
- Add table-driven tests asserting sticky requests carry ExpiryTime
- Never hand-write CreateTaskQueueRequest in tools/tests without the validation helper
When it happens
Trigger: Calling PersistenceManager.CreateTaskQueue with request.TaskQueueInfo.Kind == enumspb.TASK_QUEUE_KIND_STICKY while TaskQueueInfo.ExpiryTime is nil (also requires LastUpdateTime set, otherwise the earlier LastUpdateTime panic fires first).
Common situations: Hand-constructing CreateTaskQueueRequest in tests or tools without setting ExpiryTime; a worker client library or matching-service code path that changed Kind to sticky after a refactor and forgot to set the expiry; version skew where an older frontend builds sticky queue info without ExpiryTime.
Related errors
- UpdateTaskQueue 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/a86a71872280f084.
Report an issue: GitHub.