temporalio/temporal · error
CreateTaskQueue encountered ExpiryTime not set for sticky…
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.
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…
- unsupported partition kind:
- UpdateTaskQueue encountered LastUpdateTime not set
- Can not append slice to existing list of slices, incoming…
- enqueue task request task is nil
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/a86a71872280f084.
Report an issue: GitHub.
Appendix: 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)