{"record":{"id":"00a6d009fb6e2bee","repo":"temporalio/temporal","slug":"history-task-from-queue-has-nil-blob","errorCode":null,"errorMessage":"history task from queue has nil blob","messagePattern":"history task from queue has nil blob","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/persistence/history_task_queue_manager.go","lineNumber":42,"sourceCode":"\t// ErrMsgDeserializeRawHistoryTask is returned when the raw task cannot be deserialized from the task queue. This error\n\t// is returned when this whole top-level proto cannot be deserialized.\n\t//  Raw Task (a proto): <-- when this cannot be deserialized\n\t//\t- ShardID\n\t//\t- Blob (a serialized task)\n\tErrMsgDeserializeRawHistoryTask = \"failed to deserialize raw history task from task queue\"\n\t// ErrMsgDeserializeHistoryTask is returned when the history task cannot be deserialized from the task queue. This\n\t// error is returned when the blob inside the raw task cannot be deserialized.\n\t//  Raw Task (a proto):\n\t//\t- ShardID\n\t//\t- Blob (a serialized task) <-- when this cannot be deserialized\n\tErrMsgDeserializeHistoryTask = \"failed to deserialize history task blob\"\n\t// ErrMsgFailedToParseCategoryID is returned when category id cannot be parsed as an integer value.\n\tErrMsgFailedToParseCategoryID = \"failed to parse category id from queue name\"\n)\n\nvar (\n\tErrReadTasksNonPositivePageSize = errors.New(\"page size to read history tasks must be positive\")\n\tErrHistoryTaskBlobIsNil         = errors.New(\"history task from queue has nil blob\")\n\tErrEnqueueTaskRequestTaskIsNil  = errors.New(\"enqueue task request task is nil\")\n\tErrQueueAlreadyExists           = errors.New(\"queue already exists\")\n\tErrShardIDInvalid               = errors.New(\"shard ID must be greater than 0\")\n\tErrInvalidQueueName             = errors.New(\"invalid queue name, expected 4 fields\")\n)\n\nfunc NewHistoryTaskQueueManager(\n\tqueue QueueV2,\n\tserializer serialization.Serializer,\n) *HistoryTaskQueueManagerImpl {\n\treturn &HistoryTaskQueueManagerImpl{\n\t\tqueue:      queue,\n\t\tserializer: serializer,\n\t}\n}\n\nfunc (m *HistoryTaskQueueManagerImpl) EnqueueTask(\n\tctx context.Context,","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/persistence/history_task_queue_manager.go#L24-L60","documentation":"ErrHistoryTaskBlobIsNil (common/persistence/history_task_queue_manager.go:42) is returned by ReadTasks when a raw history task read from the queue has a nil Blob. The queue stores tasks as serialized blobs; a nil blob cannot be deserialized, so ReadTasks wraps the failure in a serialization.NewDeserializationError with ENCODING_TYPE_PROTO3. This indicates corrupted or improperly persisted task data in the queue.","triggerScenarios":"Calling HistoryTaskQueueManagerImpl.ReadTasks (or ReadRawTasks feeding it) and encountering a queue record whose Blob field is nil, at history_task_queue_manager.go:159.","commonSituations":"Storage records written by an older/buggy writer version; data corruption in the queue's underlying persistence; manual edits or partial writes to the tasks table; replication issues leaving incomplete task rows.","solutions":["Delete/skip the corrupt task record and let the task be re-enqueued or regenerated by its producer (history service)","Inspect the queue's underlying persistence rows to find how many tasks have nil blobs and check recent writer versions","Upgrade/fix the writer path that persisted tasks without blobs","Acknowledge the task via ack manager so the poison record does not block the queue's read pointer"],"exampleFix":"// producer side: before\nqueue.EnqueueTask(ctx, &persistence.Task{TaskID: id}) // blob never set\n// after\ndata, err := serializer.SerializeHistoryTask(task)\nif err != nil { return err }\nqueue.EnqueueTask(ctx, &persistence.Task{TaskID: id, Blob: data})","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isNilBlobErr(err error) bool {\n    var de *serialization.DataError\n    return errors.As(err, &de) && strings.Contains(err.Error(), persistence.ErrHistoryTaskBlobIsNil.Error())\n}","tryCatchPattern":"tasks, err := mgr.ReadTasks(ctx, req)\nif err != nil {\n    if isNilBlobErr(err) {\n        logger.Error(\"nil-blob history task encountered; acking to skip poison record\", tag.Error(err))\n        return ackAndSkip(err) // advance past corrupt record\n    }\n    return err\n}","preventionTips":["Ensure EnqueueTask always serializes and sets Blob (nil Task is rejected earlier by ErrEnqueueTaskRequestTaskIsNil)","Test producer code paths for tasks that fail to build (nil task/blob)","Monitor deserialization-error metrics on history task queues","Add a storage integrity check for task rows with NULL blob columns"],"tags":["go","persistence","task-queue","deserialization","data-corruption"],"backgroundTag":"task-blob-corruption","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}