MassTransit/MassTransit · error · SagaException

Saga update failed

Error message

Saga update failed

What it means

DynamoDbDatabaseContext<TSaga>.Update wraps any exception from the DynamoDB UpdateItemAsync call in a SagaException with message "Saga update failed". The optimistic-version ConditionalCheckFailedException is handled separately (as DynamoDbSagaConcurrencyException), so this error means the update failed for any other reason — AWS client errors, throttling, table access, serialization, etc. The original exception is preserved as InnerException.

Solutions

  1. Inspect exception.InnerException (the SagaException wraps the original) to identify the root AWS/serialization error
  2. Verify the DynamoDB table exists, is active, and that Config.OverrideTableName/TableNamePrefix match the deployed table
  3. Check AWS credentials, region, and IAM permissions (dynamodb:UpdateItem) for the running process
  4. Enable auto-scaling or on-demand capacity / add retry policy, since throttled writes surface here
  5. Ensure the saga type is serializable by System.Text.Json (public setters, no unsupported property types)

Example fix

// before
await context.Update(instance); // throws SagaException with opaque inner error

// after
try
{
    await context.Update(instance);
}
catch (SagaException ex)
{
    _logger.LogError(ex.InnerException, "Saga {CorrelationId} update failed", instance.CorrelationId);
    throw;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// validate before consuming
if (string.IsNullOrWhiteSpace(_dynamoDbConfig.OverrideTableName) == false)
    await EnsureTableExistsAsync(_dynamoDbConfig.OverrideTableName); // fail fast on missing/misconfigured table

Type guard

bool IsRetryableAwsError(Exception ex) => ex is AmazonServiceException { StatusCode: System.Net.HttpStatusCode.InternalServerError } || ex is AmazonDynamoDBException ddb && ddb.ErrorCode == "ThrottlingException";

Try / catch

try
{
    await context.Update(saga);
}
catch (SagaException ex)
{
    _logger.LogError(ex.InnerException, "Saga update failed for {CorrelationId}", saga.CorrelationId);
    throw; // let MassTransit's fault/retry pipeline handle it
}
catch (DynamoDbSagaConcurrencyException)
{
    // version conflict: reload and reapply or skip
}

Prevention

When it happens

Trigger: Calling Update during saga consumption when the DynamoDB UpdateItemAsync throws anything other than ConditionalCheckFailedException: e.g. AmazonDynamoDBException (table missing, throttled throughput, provisioning errors), AmazonServiceException (AWS outage/credentials), JsonException when serializing the saga, or network failures.

Common situations: Misconfigured AWS credentials/region so calls are rejected; the DynamoDB table or its GSI was deleted or renamed (OverrideTableName/TableNamePrefix mismatch); provisioned throughput exceeded during message bursts; saga instance not serializable with System.Text.Json options; transient network partition between the service and DynamoDB.

Related errors


AI-assisted analysis of MassTransit/MassTransit@62ab339afa (2026-09-13). Data as JSON: /api/errors/09f40f1eb5df4122. Report an issue: GitHub.

Appendix: source

Thrown at src/Persistence/MassTransit.DynamoDbIntegration/DynamoDbIntegration/Saga/DynamoDbDatabaseContext.cs:79

                var updateSaga = GetDynamoDbSaga(instance);

                await _database.GetTargetTable<DynamoDbSaga>(new GetTargetTableConfig
                {
                    Conversion = _options.Config.Conversion,
                    IsEmptyStringValueEnabled = _options.Config.IsEmptyStringValueEnabled,
                    OverrideTableName = _options.Config.OverrideTableName,
                    TableNamePrefix = _options.Config.TableNamePrefix
                })
                    .UpdateItemAsync(updateSaga.ToDocument(), new Primitive(updateSaga.CorrelationId), new Primitive(DynamoDbSaga.DefaultEntityType),
                        operationConfig);
            }
            catch (ConditionalCheckFailedException)
            {
                throw new DynamoDbSagaConcurrencyException("Saga version conflict", typeof(TSaga), instance.CorrelationId);
            }
            catch (Exception exception)
            {
                throw new SagaException("Saga update failed", typeof(TSaga), instance.CorrelationId, exception);
            }
        }

        public Task Delete(SagaConsumeContext<TSaga> context)
        {
            return _database.DeleteAsync(new DynamoDbSaga { CorrelationId = _options.FormatSagaKey(context.Saga.CorrelationId) }, new DeleteConfig
            {
                Conversion = _options.Config.Conversion,
                IsEmptyStringValueEnabled = _options.Config.IsEmptyStringValueEnabled,
                OverrideTableName = _options.Config.OverrideTableName,
                SkipVersionCheck = _options.Config.SkipVersionCheck,
                TableNamePrefix = _options.Config.TableNamePrefix
            });
        }

        public void Dispose()
        {
            _database?.Dispose();

View on GitHub (pinned to 62ab339afa)