OrchardCMS/OrchardCore · error · InvalidOperationException

The tenant ' ' can't be removed as it is neither…

Error message

The tenant '{settings.Name}' can't be removed as it is neither uninitialized nor disabled.

What it means

CheckCanRemoveShell throws InvalidOperationException when the target tenant is not removable: it is neither uninitialized nor disabled, or a shell for it is still active in at least one scope. Only uninitialized or disabled, inactive tenants may be deleted.

Solutions

  1. Disable the tenant first and wait for it to become inactive, then remove it.
  2. Retry after in-flight requests complete — active scopes release the shell when requests finish.
  3. Check settings.IsRemovable() and ensure no active scopes exist before attempting removal.

Example fix

// before
await _shellHost.RemoveShellSettingsAsync(settings);
// after
settings.State = TenantState.Disabled;
await _shellHost.UpdateShellSettingsAsync(settings);
// ensure no active scopes, then:
await _shellHost.RemoveShellSettingsAsync(settings);
Defensive patterns

Strategy: validation

Validate before calling

if (!settings.IsRemovable() || settings.Name == ShellSettings.DefaultShellName)
{
    // disable first, then remove after in-flight requests drain
    settings.State = TenantState.Disabled;
    await shellHost.UpdateShellSettingsAsync(settings);
}

Prevention

When it happens

Trigger: Calling RemoveShellSettingsAsync/RemoveShellContextAsync for a running (enabled and started) tenant, or for a disabled tenant whose shell context is still held by active scopes.

Common situations: Deleting a live tenant from a script without disabling it first; removing a tenant that was just disabled but still serves in-flight requests; UI logic racing with active request scopes holding the shell.

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/e7fe4481040752be. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore/OrchardCore/Shell/ShellHost.cs:523

    /// Note: A disabled shell still in use will be released by its last scope, and keeping it in the list
    /// prevents a consumer from creating a new one that would have a null service provider.
    /// </summary>
    private bool CanReleaseShell(ShellSettings settings) => !settings.IsDisabled() || !IsShellActive(settings);

    /// <summary>
    /// Checks if a shell can be removed, throws an exception if the shell is neither uninitialized nor disabled.
    /// </summary>
    private void CheckCanRemoveShell(ShellSettings settings)
    {
        if (settings.IsDefaultShell())
        {
            throw new InvalidOperationException($"The '{ShellSettings.DefaultShellName}' tenant can't be removed.");
        }

        // A disabled shell may be still in use in at least one active scope.
        if (!settings.IsRemovable() || IsShellActive(settings))
        {
            throw new InvalidOperationException(
                $"The tenant '{settings.Name}' can't be removed as it is neither uninitialized nor disabled.");
        }
    }

    /// <summary>
    /// Whether or not a shell is in use in at least one active scope.
    /// </summary>
    private bool IsShellActive(ShellSettings settings) =>
        settings is { Name: not null } &&
        _shellContexts.TryGetValue(settings.Name, out var context) &&
        context.IsActive();

    public void Dispose()
    {
        foreach (var shell in ListShellContexts())
        {
            shell.Dispose();
        }

View on GitHub (pinned to 4306c0717f)