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

  1. 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.
  2. Derive the expiry from the client's StickyTaskQueueScheduleToStartTimeout / worker sticky settings rather than leaving it nil.
  3. If the queue is genuinely not sticky, verify Kind is left as TASK_QUEUE_KIND_NORMAL so the ExpiryTime check is bypassed.
  4. 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

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


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/a86a71872280f084. Report an issue: GitHub.