dotnet/orleans · error · ArgumentException
Journal metadata property '{key}' is provider-owned.
Error message
Journal metadata property '{key}' is provider-owned. What it means
Thrown by AzureBlobJournalStorage when a caller-supplied metadata property key collides with a provider-reserved key. The provider owns 'format', 'checkpoint', 'checkpoint_offset', 'wal_generation' (case-insensitive), and any key starting with '$', because those carry the WAL manifest and recovery bookkeeping. Allowing a caller to overwrite them would corrupt journal recovery.
Source
Thrown at src/Azure/Orleans.Journaling.AzureStorage/AzureBlobJournalStorage.cs:1024
}
private static void ValidateCallerMetadataProperty(string key, string value)
{
ValidateCallerMetadataPropertyName(key);
ArgumentNullException.ThrowIfNull(value);
}
private static void ValidateCallerMetadataPropertyName(string key)
{
ArgumentException.ThrowIfNullOrWhiteSpace(key);
if (key.IndexOf('\0') >= 0)
{
throw new ArgumentException("Journal metadata property names must not contain null characters.", nameof(key));
}
if (IsProviderMetadataKey(key))
{
throw new ArgumentException($"Journal metadata property '{key}' is provider-owned.", nameof(key));
}
}
private static bool IsProviderMetadataKey(string key)
=> string.Equals(key, FormatMetadataKey, StringComparison.OrdinalIgnoreCase)
|| string.Equals(key, CheckpointMetadataKey, StringComparison.OrdinalIgnoreCase)
|| string.Equals(key, CheckpointOffsetMetadataKey, StringComparison.OrdinalIgnoreCase)
|| string.Equals(key, WalGenerationMetadataKey, StringComparison.OrdinalIgnoreCase)
|| key.StartsWith("$", StringComparison.Ordinal);
private static ETag ToAzureETag(string eTag)
{
ArgumentException.ThrowIfNullOrWhiteSpace(eTag);
return new ETag(eTag);
}
/// <summary>
/// Returns true when an Azure response indicates an append blob was sealed (HTTP 409 / BlobIsSealed).View on GitHub (pinned to fca799fa70)
Solutions
- Prefix your application metadata keys with a namespace, e.g. 'app.format' or 'user.checkpoint'.
- Avoid keys starting with '$' entirely; that prefix is reserved for future provider bookkeeping.
- Check IsProviderMetadataKey semantics: the four named keys plus any '$'-prefixed key are off-limits.
Example fix
// before
var meta = new Dictionary<string, string> { ["format"] = "json", ["checkpoint"] = "v1" };
await storage.AppendAsync(data, meta, ct);
// after
var meta = new Dictionary<string, string> { ["app.format"] = "json", ["app.checkpoint"] = "v1" };
await storage.AppendAsync(data, meta, ct); Defensive patterns
Strategy: validation
Validate before calling
static readonly HashSet<string> ReservedKeys = new(StringComparer.OrdinalIgnoreCase)
{ "format", "checkpoint", "checkpoint_offset", "wal_generation" };
static bool IsSafeMetadataKey(string key)
=> !string.IsNullOrWhiteSpace(key)
&& !key.StartsWith("$", StringComparison.Ordinal)
&& !ReservedKeys.Contains(key);
// usage
if (metadata.Keys.All(IsSafeMetadataKey)) { /* safe */ } Prevention
- Namespace your application metadata keys to avoid reserved names.
- Document the reserved-key list near your metadata-building helpers.
- Add a configuration-time test that validates a representative metadata dictionary.
When it happens
Trigger: Passing a metadata dictionary to AppendAsync/ReplaceAsync/CreateIfNotExistsAsync whose key is exactly one of the reserved names (case-insensitive) or starts with '$'. The check runs in ValidateCallerMetadataPropertyName before the write.
Common situations: Using generic key names like 'format' or 'checkpoint' for application metadata without realizing they are reserved; migrating from a different storage backend that allowed those names; using a '$'-prefixed convention copied from Azure Cosmos DB.
Related errors
- Journal metadata property names must not contain null charac
- MaxMetadataOnlyConflictRetries must be non-negative.
- MetadataOnlyConflictInitialBackoff must be non-negative.
- MetadataOnlyConflictMaxBackoff must be non-negative.
- The journal id must not be the default value.
AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13).
Data as JSON: /api/errors/0d529fd779d7b097.
Report an issue: GitHub.