OrchardCMS/OrchardCore · error · InvalidOperationException

Invalid serial number for shell descriptor

Error message

Invalid serial number for shell descriptor

What it means

ShellDescriptorManager.UpdateShellDescriptorAsync uses optimistic concurrency: the caller passes the SerialNumber it previously read, and the manager compares it to the current ShellDescriptor's SerialNumber before applying updates. A mismatch means the descriptor changed concurrently, so it throws InvalidOperationException to prevent clobbering.

Solutions

  1. Re-read the current ShellDescriptor and retry the update with its fresh SerialNumber.
  2. Serialize shell updates (avoid parallel feature-enable calls for the same tenant).
  3. Catch InvalidOperationException and surface a 'conflict, please retry' message rather than crashing the request.

Example fix

// before
await manager.UpdateShellDescriptorAsync(staleSerialNumber, features); // descriptor changed meanwhile
// after
var descriptor = await manager.GetShellDescriptorAsync();
await manager.UpdateShellDescriptorAsync(descriptor.SerialNumber, features);
Defensive patterns

Strategy: retry

Validate before calling

var descriptor = await manager.GetShellDescriptorAsync();
if (descriptor is null || descriptor.SerialNumber != priorSerialNumber) priorSerialNumber = descriptor?.SerialNumber ?? 0;

Try / catch

try { await manager.UpdateShellDescriptorAsync(priorSerialNumber, features); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Invalid serial number")) { /* reload descriptor and retry */ }

Prevention

When it happens

Trigger: Calling UpdateShellDescriptorAsync with a priorSerialNumber that differs from the currently stored ShellDescriptor.SerialNumber — e.g. two features enabling simultaneously, or the caller read the descriptor long before updating.

Common situations: Concurrent feature enable/disable in the admin UI from multiple tabs; module startup code caching a shell descriptor across requests; race between recipe execution and user actions.

Related errors


AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13). Data as JSON: /api/errors/6120bc99e4677957. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore/OrchardCore.Infrastructure/Shell/ShellDescriptorManager.cs:99

        var missingDependencies = (await _extensionManager.LoadFeaturesAsync(featureIds))
            .Select(entry => entry.Id)
            .Except(featureIds)
            .Select(id => new ShellFeature(id));

        shellDescriptor.Features = features
            .Concat(missingDependencies)
            .ToList();

        return _shellDescriptor = shellDescriptor;
    }

    public async Task UpdateShellDescriptorAsync(int priorSerialNumber, IEnumerable<ShellFeature> enabledFeatures)
    {
        var shellDescriptor = await _documentStore.GetOrCreateMutableAsync<ShellDescriptor>();

        if (priorSerialNumber != shellDescriptor.SerialNumber)
        {
            throw new InvalidOperationException("Invalid serial number for shell descriptor");
        }

        if (_logger.IsEnabled(LogLevel.Information))
        {
            _logger.LogInformation("Updating shell descriptor for tenant '{TenantName}' ...", _shellSettings.Name);
        }

        shellDescriptor.SerialNumber++;

        shellDescriptor.Features = _alwaysEnabledFeatures.Union(enabledFeatures).ToList();
        foreach (var feature in shellDescriptor.Features)
        {
            if (shellDescriptor.Installed.Contains(feature))
            {
                continue;
            }

            var installed = new InstalledShellFeature(feature)

View on GitHub (pinned to 4306c0717f)