dotnet/orleans · error · InvalidOperationException

Table has a status of and can't be updated automatically.

Error message

Table {tableDescription.TableName} has a status of {tableDescription.TableStatus} and can't be updated automatically.

What it means

Thrown by DynamoDBStorage.UpdateTableAsync when the existing table's TableStatus is not one of CREATING, UPDATING, or ACTIVE (the set held by _updateTableValidTableStatuses) and updateIfExists is true. The library only auto-updates tables it considers healthy/transitioning; statuses like DELETING, ARCHIVED, or INACCESSIBLE_ENCRYPTION_CREDENTIALS are rejected so it does not fight an in-flight lifecycle operation.

Solutions

  1. Wait for the table to return to ACTIVE (or finish DELETING) and restart the silo, or recreate the table.
  2. If the table is in a bad state (ARCHIVED / INACCESSIBLE_ENCRYPTION_CREDENTIALS), resolve the underlying AWS issue (restore KMS key access, restore the table).
  3. If you intentionally manage the schema out-of-band, set UpdateIfExists = false so the provider does not attempt auto-update.
  4. Confirm no concurrent operator/script is deleting or archiving the table during deployments.

Example fix

// before: provider tries to update a table that is DELETING/ARCHIVED
opt.UpdateIfExists = true;

// after: either let the table settle to ACTIVE, or disable auto-update
opt.UpdateIfExists = false; // you manage schema out-of-band
// then ensure the table is ACTIVE before the silo starts
Defensive patterns

Strategy: validation

Validate before calling

// Before startup, ensure the target table is in an updatable status (or absent so it can be created)
var statuses = new[] { TableStatus.CREATING, TableStatus.UPDATING, TableStatus.ACTIVE };
var desc = await DescribeExistingTableAsync(tableName);
if (desc is not null && !statuses.Contains(desc.TableStatus) && opt.UpdateIfExists)
    throw new InvalidOperationException($"Table {tableName} is {desc.TableStatus}; resolve before starting the silo");

Type guard

static bool IsUpdatableStatus(TableStatus s) =>
    s == TableStatus.CREATING || s == TableStatus.UPDATING || s == TableStatus.ACTIVE;

Try / catch

try { await silo.StartAsync(); }
catch (InvalidOperationException ix) when (ix.Message.Contains("can't be updated automatically"))
{
    _logger.LogCritical("DynamoDB table is in a non-updatable status; resolve (restore/delete) or set UpdateIfExists=false");
    throw;
}

Prevention

When it happens

Trigger: Produced during provider init when the table already exists, UpdateIfExists is true, and DescribeTable returns a status outside the valid set. Triggered by a table currently being deleted, an archived table being reused, or a table whose KMS encryption key became inaccessible.

Common situations: Reusing a table that someone started deleting out-of-band; pointing the provider at an archived table; a KMS key rotation that left the table INACCESSIBLE_ENCRYPTION_CREDENTIALS; manual table operations colliding with silo startup.

Related errors


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

Appendix: source

Thrown at src/AWS/Shared/Storage/DynamoDBStorage.cs:267

            }
            catch (Exception exc)
            {
                LogErrorCouldNotCreateTable(_logger, exc, tableName);
                throw;
            }
        }

        private async ValueTask UpdateTableAsync(TableDescription tableDescription, List<AttributeDefinition> attributes, List<GlobalSecondaryIndex>? secondaryIndexes = null, string? ttlAttributeName = null, CancellationToken cancellationToken = default)
        {
            if (!this._updateIfExists)
            {
                LogWarningTableNotUpdated(_logger, tableDescription.TableName);
                return;
            }

            if (!_updateTableValidTableStatuses.Contains(tableDescription.TableStatus))
            {
                throw new InvalidOperationException($"Table {tableDescription.TableName} has a status of {tableDescription.TableStatus} and can't be updated automatically.");
            }

            if (tableDescription.TableStatus == TableStatus.CREATING
                || tableDescription.TableStatus == TableStatus.UPDATING)
            {
                tableDescription = await TableWaitOnStatusAsync(tableDescription.TableName, tableDescription.TableStatus, TableStatus.ACTIVE, cancellationToken: cancellationToken);
            }

            var request = new UpdateTableRequest
            {
                TableName = tableDescription.TableName,
                AttributeDefinitions = attributes,
                BillingMode = this._useProvisionedThroughput ? BillingMode.PROVISIONED : BillingMode.PAY_PER_REQUEST,
                ProvisionedThroughput = _provisionedThroughput,
                GlobalSecondaryIndexUpdates = this._useProvisionedThroughput
                    ? tableDescription.GlobalSecondaryIndexes?.Select(gsi => new GlobalSecondaryIndexUpdate
                    {
                        Update = new UpdateGlobalSecondaryIndexAction

View on GitHub (pinned to fca799fa70)