This error occurs when you submit a DynamoDB transaction (TransactWriteItems or ExecuteTransaction) using a client request token that is already being used by an ongoing transaction. It indicates a duplicate transaction attempt within the 10-minute idempotency window.
The TransactionInProgressException is thrown by AWS SDK or CLI when you attempt to submit a transaction request with a ClientRequestToken that already has an in-flight transaction using the same token. DynamoDB uses client request tokens to ensure idempotent transactions. A token is valid for 10 minutes after the request that initially used it completes. This means within that 10-minute window, DynamoDB prevents duplicate transactions using the same token to protect data consistency. This HTTP 400 client-side error indicates that either: 1. Your application is submitting a duplicate transaction request too quickly 2. The initial transaction hasn't completed yet, but you're retrying with the same token 3. Multiple processes or threads are using the same request token concurrently Unlike server-side errors, this exception means DynamoDB is working correctly by preventing accidental duplicate transactions that could cause unintended side effects.
Check that you're not submitting the same request token multiple times simultaneously. If you're retrying, generate a new token for each attempt instead of reusing the same one:
// AWS SDK v3 (JavaScript/TypeScript)
import { DynamoDBClient, TransactWriteItemsCommand } from "@aws-sdk/client-dynamodb";
import { randomUUID } from "crypto";
const client = new DynamoDBClient({});
async function submitTransaction(items) {
// Generate a UNIQUE token for each attempt, don't reuse
const requestToken = randomUUID();
const command = new TransactWriteItemsCommand({
TransactItems: items,
ClientRequestToken: requestToken // Each attempt gets a new token
});
try {
const response = await client.send(command);
console.log("Transaction succeeded:", response);
return response;
} catch (error) {
if (error.name === "TransactionInProgressException") {
// DO NOT retry with the same token
// Instead, either wait and retry with a new token, or fail
console.error("Transaction still in progress, please retry later with a new token");
throw error;
}
throw error;
}
}# AWS SDK (Python/boto3)
import uuid
import boto3
from botocore.exceptions import ClientError
dynamodb = boto3.client('dynamodb')
def submit_transaction(transact_items):
# Generate a unique token for each attempt
request_token = str(uuid.uuid4())
try:
response = dynamodb.transact_write_items(
TransactItems=transact_items,
ClientRequestToken=request_token
)
print("Transaction succeeded")
return response
except ClientError as e:
if e.response['Error']['Code'] == 'TransactionInProgressException':
# Do not retry with the same token
print("Transaction in progress, retry with a new token later")
raise
raiseThe key principle: each attempt gets a new, unique token. Never retry with the original token.
Configure appropriate timeout values in your SDK to avoid premature retries while the transaction is still processing:
// AWS SDK v3 configuration with proper timeouts
const client = new DynamoDBClient({
maxAttempts: 3,
retryStrategy: new DefaultRetryStrategy(
3, // max attempts
(attempt) => {
// Exponential backoff: 100ms, 200ms, 400ms
return Math.min(Math.pow(2, attempt) * 100, 1000);
}
)
});
// For individual transactions, ensure socket timeout is set appropriately
const command = new TransactWriteItemsCommand({
TransactItems: items,
ClientRequestToken: randomUUID()
});
const response = await Promise.race([
client.send(command),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Transaction timeout")), 30000) // 30 second timeout
)
]);# AWS SDK v3 configuration (Python/boto3)
import boto3
from botocore.config import Config
# Configure proper retry settings
config = Config(
max_pool_connections=50,
retries={
'max_attempts': 3,
'mode': 'adaptive'
},
connect_timeout=5,
read_timeout=60 # 60 second read timeout for transactions
)
dynamodb = boto3.client('dynamodb', config=config)
def submit_transaction_with_timeout(transact_items):
import time
start_time = time.time()
timeout_seconds = 30
while time.time() - start_time < timeout_seconds:
try:
response = dynamodb.transact_write_items(
TransactItems=transact_items,
ClientRequestToken=str(uuid.uuid4())
)
return response
except ClientError as e:
if e.response['Error']['Code'] == 'TransactionInProgressException':
time.sleep(1) # Wait before retrying with new token
continue
raise
raise TimeoutError("Transaction did not complete within timeout")These settings ensure the SDK gives transactions enough time to complete before treating it as failed.
In distributed systems with multiple instances, ensure each one generates unique tokens rather than sharing the same token across instances:
// BAD: Multiple instances using the same hardcoded token
const GLOBAL_TOKEN = "my-fixed-token"; // Don't do this!
// GOOD: Each instance generates unique tokens
import { randomUUID } from "crypto";
class TransactionManager {
constructor() {
this.instanceId = randomUUID(); // Unique per instance
}
async executeTransaction(items) {
// Combine instance ID with timestamp for uniqueness
const requestToken = `${this.instanceId}-${Date.now()}-${Math.random()}`;
const command = new TransactWriteItemsCommand({
TransactItems: items,
ClientRequestToken: requestToken
});
return this.client.send(command);
}
}
const manager = new TransactionManager();# Better token strategy for distributed systems
import uuid
from datetime import datetime
class TransactionManager:
def __init__(self, instance_id=None):
self.instance_id = instance_id or str(uuid.uuid4())
def generate_request_token(self):
"""Generate a globally unique request token"""
timestamp = datetime.utcnow().isoformat()
random_part = str(uuid.uuid4())
return f"{self.instance_id}-{timestamp}-{random_part}"
def submit_transaction(self, transact_items):
request_token = self.generate_request_token()
response = dynamodb.transact_write_items(
TransactItems=transact_items,
ClientRequestToken=request_token
)
return responseThis ensures tokens are never duplicated across instances or requests.
The error often occurs when these timeout values are too aggressive. AWS recommends specific settings for TransactWriteItems:
// AWS SDK v3 with proper DynamoDB transaction settings
const client = new DynamoDBClient({
requestHandler: new NodeHttpHandler({
connectionTimeout: 5000, // 5 seconds to establish connection
socketTimeout: 30000, // 30 seconds for socket operations
requestTimeout: 60000, // 60 seconds total request timeout
abortSignal: AbortSignal.timeout(60000)
}),
maxAttempts: 3,
retryStrategy: new AdaptiveRetryStrategy(3)
});
// For JavaScript SDK v2 (if still in use)
const dynamodb = new AWS.DynamoDB({
maxRetries: 3,
httpOptions: {
timeout: 60000,
connectTimeout: 5000
}
});# Proper configuration for boto3
from botocore.config import Config
config = Config(
connect_timeout=5,
read_timeout=60,
retries={
'max_attempts': 3,
'mode': 'adaptive'
}
)
dynamodb = boto3.client('dynamodb', config=config)
# AWS recommends:
# clientExecutionTimeout >= 5 seconds (allow one retry to complete)
# socketTimeout < requestTimeout (detect stalled sockets)
# For transactions, set these to at least 30-60 secondsThese settings match AWS recommendations for TransactWriteItems operations.
Know that client tokens have a 10-minute lifespan. After 10 minutes, you must use a different token:
// Example: Token cache with expiration
class TokenCache {
constructor(ttlMs = 10 * 60 * 1000) { // 10 minutes
this.cache = new Map();
this.ttlMs = ttlMs;
}
canReuse(token) {
const entry = this.cache.get(token);
if (!entry) return false;
const age = Date.now() - entry.createdAt;
return age < this.ttlMs;
}
recordToken(token) {
this.cache.set(token, { createdAt: Date.now() });
}
cleanExpired() {
const now = Date.now();
for (const [token, entry] of this.cache.entries()) {
if (now - entry.createdAt > this.ttlMs) {
this.cache.delete(token);
}
}
}
}
const tokenCache = new TokenCache();
// When retrying:
// - Within 10 minutes: can reuse same token for exact same request
// - After 10 minutes: must use new token
// - Different request: must use new token even within 10 minutesThe idempotency window means:
- Reuse tokens only for idempotent retries of the exact same transaction
- Vary any parameter = must use a new token
- After 10 minutes = must use a new token
- Never hold tokens longer than necessary
Implement comprehensive logging to catch accidental token reuse before it becomes a production issue:
// Instrumentation for token tracking
class InstrumentedTransactionManager {
constructor() {
this.tokenLog = new Map();
}
async executeTransaction(items, options = {}) {
const requestToken = randomUUID();
const timestamp = Date.now();
// Log token usage
if (this.tokenLog.has(requestToken)) {
console.warn(`WARNING: Token ${requestToken} reused at ${new Date(timestamp).toISOString()}`);
}
this.tokenLog.set(requestToken, {
timestamp,
itemCount: items.length,
status: 'PENDING'
});
try {
const result = await client.send(
new TransactWriteItemsCommand({
TransactItems: items,
ClientRequestToken: requestToken
})
);
this.tokenLog.set(requestToken, {
...this.tokenLog.get(requestToken),
status: 'SUCCESS',
completedAt: Date.now()
});
return result;
} catch (error) {
this.tokenLog.set(requestToken, {
...this.tokenLog.get(requestToken),
status: 'FAILED',
error: error.name,
completedAt: Date.now()
});
// Send to monitoring (CloudWatch, DataDog, etc.)
if (error.name === 'TransactionInProgressException') {
console.error(`Token ${requestToken} caused TransactionInProgressException`);
// This indicates incorrect retry logic
}
throw error;
}
}
getTokenStats() {
// Analyze patterns: token reuse, failure rates, timing
return Array.from(this.tokenLog.entries()).map(([token, data]) => ({
token,
...data
}));
}
}This helps identify where duplicate tokens are coming from.
Client Request Token Details:
A ClientRequestToken is a string (up to 36 characters, typically a UUID) you provide when calling TransactWriteItems or ExecuteTransaction. DynamoDB uses this token to make your transactions idempotent—meaning if you submit the exact same request twice with the same token within 10 minutes, DynamoDB returns success without applying the second transaction.
The 10-minute window starts when the first request using that token completes, not when it begins. If a transaction takes 5 minutes to complete, you have only 5 minutes left in the idempotency window for that token.
Comparison with IdempotentParameterMismatch:
If you retry with the same token but change a parameter (different items, different conditions, etc.), DynamoDB returns IdempotentParameterMismatch instead of TransactionInProgressException. This protects against accidentally modifying behavior on retry.
Impact on Read/Write Capacity:
TransactionInProgressException doesn't consume read or write capacity units since the transaction was rejected before execution. However, the initial failed attempt and subsequent retries with new tokens all consume capacity.
SDK Default Behavior:
Most AWS SDKs have automatic retry logic for transient errors, but they DON'T automatically handle TransactionInProgressException by changing the token. You must either:
1. Wait and retry manually with a new token
2. Implement a custom retry strategy that generates new tokens
3. Accept the error as a symptom of downstream issues (timeouts, network problems) and fail fast
Testing Transactions:
Use DynamoDB Local to test transaction behavior during development. You can trigger TransactionInProgressException in a test environment by simulating network delays or submitting duplicate tokens programmatically.
Migration from Single-Item Operations:
If migrating from PutItem/UpdateItem to TransactWriteItems, remember to generate request tokens. Single-item operations don't require tokens, but transactions do. Ensure your token generation is properly seeded before production cutover.
ValidationException: The provided key element does not match the schema
How to fix "ValidationException: The provided key element does not match the schema" in DynamoDB
UnrecognizedClientException: The security token included in the request is invalid
How to fix "UnrecognizedClientException: The security token included in the request is invalid" in DynamoDB
TransactionCanceledException: Transaction cancelled
How to fix "TransactionCanceledException: Transaction cancelled" in DynamoDB
RequestLimitExceeded: Throughput exceeds the current throughput limit for your account
How to fix "RequestLimitExceeded: Throughput exceeds the current throughput limit for your account" in DynamoDB
InternalServerError: Internal Server Error
How to fix "InternalServerError" in DynamoDB