{"record":{"id":"748d07116128664d","repo":"temporalio/temporal","slug":"updatetaskqueue-encountered-expirytime-not-set-for","errorCode":null,"errorMessage":"UpdateTaskQueue encountered ExpiryTime not set for sticky task queue","messagePattern":"UpdateTaskQueue encountered ExpiryTime not set for sticky task queue","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/persistence/task_manager.go","lineNumber":84,"sourceCode":"\t\tExpiryTime:    taskQueueInfo.ExpiryTime,\n\t\tTaskQueueInfo: taskQueueInfoBlob,\n\t}\n\tif err := m.taskStore.CreateTaskQueue(ctx, internalRequest); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &CreateTaskQueueResponse{}, nil\n}\n\nfunc (m *taskManagerImpl) UpdateTaskQueue(\n\tctx context.Context,\n\trequest *UpdateTaskQueueRequest,\n) (*UpdateTaskQueueResponse, error) {\n\ttaskQueueInfo := request.TaskQueueInfo\n\tif taskQueueInfo.LastUpdateTime == nil {\n\t\tpanic(\"UpdateTaskQueue encountered LastUpdateTime not set\")\n\t}\n\tif taskQueueInfo.ExpiryTime == nil && taskQueueInfo.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY {\n\t\tpanic(\"UpdateTaskQueue encountered ExpiryTime not set for sticky task queue\")\n\t}\n\ttaskQueueInfoBlob, err := m.serializer.TaskQueueInfoToBlob(taskQueueInfo)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinternalRequest := &InternalUpdateTaskQueueRequest{\n\t\tNamespaceID:   request.TaskQueueInfo.GetNamespaceId(),\n\t\tTaskQueue:     request.TaskQueueInfo.GetName(),\n\t\tTaskType:      request.TaskQueueInfo.GetTaskType(),\n\t\tRangeID:       request.RangeID,\n\t\tTaskQueueInfo: taskQueueInfoBlob,\n\n\t\tTaskQueueKind: request.TaskQueueInfo.GetKind(),\n\t\tExpiryTime:    taskQueueInfo.ExpiryTime,\n\n\t\tPrevRangeID: request.PrevRangeID,\n\t}","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/persistence/task_manager.go#L66-L102","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Preserve or refresh ExpiryTime (time.Now().UTC().Add(stickyTTL)) whenever Kind is sticky on update.","If the queue should no longer be sticky, update with Kind TASK_QUEUE_KIND_NORMAL so ExpiryTime is not required.","When loading task queue info from storage for an update, re-validate sticky invariants before mutating.","Add a validation helper applied to every UpdateTaskQueueRequest build site."],"exampleFix":"// before\nupdateInfo := loadedInfo // Kind sticky, ExpiryTime lost in deserialization\n_, err := taskManager.UpdateTaskQueue(ctx, &persistence.UpdateTaskQueueRequest{TaskQueueInfo: updateInfo})\n// after\nif updateInfo.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY && updateInfo.ExpiryTime == nil {\n    updateInfo.ExpiryTime = timestamp.TimePtr(time.Now().UTC().Add(stickyTaskQueueTTL))\n}\n_, err := taskManager.UpdateTaskQueue(ctx, &persistence.UpdateTaskQueueRequest{TaskQueueInfo: updateInfo})","handlingStrategy":"validation","validationCode":"func validateTQUpdate(tq *persistencespb.TaskQueueInfo) error {\n    if tq.LastUpdateTime == nil { return errors.New(\"LastUpdateTime not set\") }\n    if tq.ExpiryTime == nil && tq.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY {\n        return errors.New(\"sticky task queue requires ExpiryTime\")\n    }\n    return nil\n}","typeGuard":"func stickyExpiryMissing(tq *persistencespb.TaskQueueInfo) bool { return tq.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY && tq.ExpiryTime == nil }","tryCatchPattern":"// pre-validate; a panic here would take down the whole service process\nif err := validateTQUpdate(tqInfo); err != nil { return nil, err }\n_, err := mgr.UpdateTaskQueue(ctx, &persistence.UpdateTaskQueueRequest{TaskQueueInfo: tqInfo})","preventionTips":["Refresh ExpiryTime on every sticky update rather than copying stale info","Re-derive sticky invariants after any Kind change or blob deserialization","Unit-test the full Create/Update lifecycle of sticky queues with expiry assertions"],"tags":["persistence","panic","task-queue","sticky","expiry"],"backgroundTag":"missing-expiry-time-for-sticky-task-queue","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}