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

  1. Prefix your application metadata keys with a namespace, e.g. 'app.format' or 'user.checkpoint'.
  2. Avoid keys starting with '$' entirely; that prefix is reserved for future provider bookkeeping.
  3. 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

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


AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13). Data as JSON: /api/errors/0d529fd779d7b097. Report an issue: GitHub.