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

  1. Preserve or refresh ExpiryTime (time.Now().UTC().Add(stickyTTL)) whenever Kind is sticky on update.
  2. If the queue should no longer be sticky, update with Kind TASK_QUEUE_KIND_NORMAL so ExpiryTime is not required.
  3. When loading task queue info from storage for an update, re-validate sticky invariants before mutating.
  4. 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

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


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