dotnet/orleans · critical · InvalidOperationException
Azure Table journal header state does not include a generati
Error message
Azure Table journal header state does not include a generation.
What it means
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.
Source
Thrown at src/Azure/Orleans.Journaling.AzureStorage/AzureTableJournalStorage.cs:255
// Appends are written as one entity group transaction, so validate its limits before touching storage.
ThrowIfBatchTooLarge(value.Length);
var startTimestamp = Stopwatch.GetTimestamp();
var succeeded = false;
try
{
for (var attempt = 0; ; attempt++)
{
// Ensure local state has the current header ETag and manifest before making a conditional write.
if (!HeaderExists)
{
await EnsureHeaderAsync(cancellationToken).ConfigureAwait(false);
}
var expectedETag = _headerETag;
var expectedProviderState = _headerProviderState;
var generation = expectedProviderState.Generation
?? throw new InvalidOperationException("Azure Table journal header state does not include a generation.");
var entities = CreateDataEntities(value, generation, firstSequence: expectedProviderState.RowCount);
var newProviderState = expectedProviderState with
{
RowCount = checked(expectedProviderState.RowCount + entities.Count),
Length = checked(expectedProviderState.Length + value.Length),
AppendRowCount = checked(expectedProviderState.AppendRowCount + entities.Count),
AppendLength = checked(expectedProviderState.AppendLength + value.Length),
};
// Guard the whole batch with the last observed header ETag so appends fail if the
// journal changed since this instance recovered it.
var actions = new List<TableTransactionAction>(entities.Count + 1)
{
new(TableTransactionActionType.UpdateMerge, CreateHeaderCountsPatch(newProviderState), expectedETag),
};
foreach (var entity in entities)
{
actions.Add(new(TableTransactionActionType.Add, entity));View on GitHub (pinned to fca799fa70)
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.
Defensive patterns
Strategy: try-catch
Try / catch
try
{
await storage.AppendAsync(value, ct);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("does not include a generation"))
{
logger.LogWarning(ex, "Header missing generation; triggering compaction.");
await storage.CompactAsync(ct);
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Azure Blob journal WAL ended before the checkpoint offset wa
- The journal storage consumer did not read all supplied journ
- Azure Table journal batch of {length:N0} bytes exceeds the p
- AzureBlobJournalStorageProvider has not been initialized. En
- Journal format key '{journalFormatKey}' requires keyed servi
AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13).
Data as JSON: /api/errors/525be67b304fbec4.
Report an issue: GitHub.