OrchardCMS/OrchardCore · error · InvalidOperationException

Can't resolve a scope on tenant

Error message

Can't resolve a scope on tenant '{Settings.Name}' as it is disabled or disposed

What it means

ShellContext.AddRef increments the shell's reference count before creating a ShellScope. ServiceProvider is null when the shell is disabled or has been disposed, making scope creation impossible, so AddRef throws InvalidOperationException naming the tenant.

Solutions

  1. Check shellContext.ServiceProvider is not null (and IsActive) before creating a scope.
  2. Re-resolve the current ShellContext via IShellContextFactory/IShellSettingsManager instead of caching stale references.
  3. Disable or guard the background work for tenants that are disabled (check ShellSettings.IsRunning / State).
  4. Retry the operation after obtaining a fresh shell context, since the old one is permanently gone.

Example fix

// before
using var scope = await _shellContext.CreateScopeAsync();
// after
if (_shellContext.ServiceProvider is null || !_shellContext.Settings.IsRunning())
{
    _shellContext = await _shellContextFactory.GetShellContextAsync(_shellContext.Settings);
}
using var scope = await _shellContext.CreateScopeAsync();
Defensive patterns

Strategy: validation

Validate before calling

if (shellContext.ServiceProvider is null || !shellContext.Settings.IsRunning()) { /* re-resolve or skip */ }

Type guard

bool CanCreateScope(ShellContext s) => s.ServiceProvider is not null && s.Settings.IsRunning();

Try / catch

try { using var scope = shellContext.CreateScope(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("disabled or disposed")) { /* re-resolve fresh shell context */ }

Prevention

When it happens

Trigger: Creating a ShellScope (ShellScope.CreateScope/UsingAsync) for a tenant whose ShellContext was disabled (e.g. tenant stopped in admin, feature-level shutdown) or already disposed (released during shutdown/rebuild).

Common situations: Background jobs or singletons holding an old shell reference after a tenant restart, requests racing tenant reload/dispose, or code resolving scopes for a disabled tenant in a multi-tenant setup.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/OrchardCore/OrchardCore.Abstractions/Shell/Builders/ShellContext.cs:249

            // Remove any previous instance that represents the same tenant in case it has been released or reloaded.
            _dependents.RemoveAll(wref => !wref.TryGetTarget(out var shell) || shell.Settings.Name == shellContext.Settings.Name);

            _dependents.Add(new WeakReference<ShellContext>(shellContext));
        }
        finally
        {
            _semaphore.Release();
        }
    }

    internal void AddRef()
    {
        // The service provider is null if we try to create
        // a scope on a disabled shell or already disposed.
        if (ServiceProvider is null)
        {
            throw new InvalidOperationException(
               $"Can't resolve a scope on tenant '{Settings.Name}' as it is disabled or disposed");
        }

        int current;
        do
        {
            current = _refCount;
            if (current < 0)
            {
                throw new InvalidOperationException(
                   $"Can't resolve a scope on tenant '{Settings.Name}' as the shell context is already terminated");
            }
            // Try to increment _refCount only if it is not <= -1
        }
        while (Interlocked.CompareExchange(ref _refCount, current + 1, current) != current);

        if (Interlocked.CompareExchange(ref _terminated, 0, 0) != 0)
        {

View on GitHub (pinned to 4306c0717f)