temporalio/temporal · error

UpdateTaskQueue encountered ExpiryTime not set for sticky…

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.

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.

Appendix: 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)