dotnet/orleans · error · InvalidOperationException

Table {tableDescription.TableName} has a status of {tableDes

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.

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)

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.