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
- Inspect exception.InnerException (the SagaException wraps the original) to identify the root AWS/serialization error
- Verify the DynamoDB table exists, is active, and that Config.OverrideTableName/TableNamePrefix match the deployed table
- Check AWS credentials, region, and IAM permissions (dynamodb:UpdateItem) for the running process
- Enable auto-scaling or on-demand capacity / add retry policy, since throttled writes surface here
- 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
- Always log SagaException.InnerException, not just the wrapper message
- Add MassTransit retry policy (UseInMemoryOutbox + retry) for transient DynamoDB throttling
- Verify table name/prefix config in integration tests before deploying
- Keep saga DTOs System.Text.Json-serializable (public setters, supported types)
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
- The future instance was not found for the specified command
- Saga update failed
- Saga version conflict
- DynamoDb saga repository does not support queries
- Value cannot be null. (Parameter 'urn')
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)