{"record":{"id":"525be67b304fbec4","repo":"dotnet/orleans","slug":"azure-table-journal-header-state-does-not-include","errorCode":null,"errorMessage":"Azure Table journal header state does not include a generation.","messagePattern":"Azure Table journal header state does not include a generation\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"critical","filePath":"src/Azure/Orleans.Journaling.AzureStorage/AzureTableJournalStorage.cs","lineNumber":255,"sourceCode":"        // Appends are written as one entity group transaction, so validate its limits before touching storage.\n        ThrowIfBatchTooLarge(value.Length);\n        var startTimestamp = Stopwatch.GetTimestamp();\n        var succeeded = false;\n\n        try\n        {\n            for (var attempt = 0; ; attempt++)\n            {\n                // Ensure local state has the current header ETag and manifest before making a conditional write.\n                if (!HeaderExists)\n                {\n                    await EnsureHeaderAsync(cancellationToken).ConfigureAwait(false);\n                }\n\n                var expectedETag = _headerETag;\n                var expectedProviderState = _headerProviderState;\n                var generation = expectedProviderState.Generation\n                    ?? throw new InvalidOperationException(\"Azure Table journal header state does not include a generation.\");\n                var entities = CreateDataEntities(value, generation, firstSequence: expectedProviderState.RowCount);\n                var newProviderState = expectedProviderState with\n                {\n                    RowCount = checked(expectedProviderState.RowCount + entities.Count),\n                    Length = checked(expectedProviderState.Length + value.Length),\n                    AppendRowCount = checked(expectedProviderState.AppendRowCount + entities.Count),\n                    AppendLength = checked(expectedProviderState.AppendLength + value.Length),\n                };\n\n                // Guard the whole batch with the last observed header ETag so appends fail if the\n                // journal changed since this instance recovered it.\n                var actions = new List<TableTransactionAction>(entities.Count + 1)\n                {\n                    new(TableTransactionActionType.UpdateMerge, CreateHeaderCountsPatch(newProviderState), expectedETag),\n                };\n                foreach (var entity in entities)\n                {\n                    actions.Add(new(TableTransactionActionType.Add, entity));","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/src/Azure/Orleans.Journaling.AzureStorage/AzureTableJournalStorage.cs#L237-L273","documentation":"Thrown during AzureTableJournalStorage.AppendAsync when the cached header provider state has no Generation value. The generation identifies the current set of data rows; without it the append cannot name its rows correctly. A missing generation means the header was loaded in an inconsistent or partial state and recovery is required before appending.","triggerScenarios":"Calling AppendAsync after EnsureHeaderAsync loaded a header whose ProviderState.Generation is null. This can happen if the header entity was written by an incompatible version, corrupted, or partially merged by a concurrent writer.","commonSituations":"Schema migration where an older header lacks the Generation property; concurrent replace that merged a header without generation; a bug in custom header serialization; reading a header written by a different (incompatible) journal implementation.","solutions":["Trigger a full recovery/compaction to rewrite the header with a valid generation.","If migrating schemas, write a one-time migration that backfills the Generation property on existing headers.","Inspect the header entity in Azure Table storage and verify the GenerationPropertyName ('Generation') is set."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try\n{\n    await storage.AppendAsync(value, ct);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"does not include a generation\"))\n{\n    logger.LogWarning(ex, \"Header missing generation; triggering compaction.\");\n    await storage.CompactAsync(ct);\n}","preventionTips":["Run schema migrations that backfill the Generation property before deploying new code.","Avoid concurrent header writes from incompatible versions.","Trigger compaction after any header schema change."],"tags":["azure-table-storage","recovery","header","generation","invalid-operation"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}