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

  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.

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)