What it means
A DynamoDB transaction is all-or-nothing across up to a hundred items. If any single operation cannot proceed, the entire transaction is cancelled and nothing is written. TransactionCanceledException reports that, and its message is deliberately vague because the detail lives somewhere the message cannot reach.
That detail is the CancellationReasons array on the exception object, and it is positional: one entry per item you submitted, in the order you submitted them. An entry of None means that operation was fine. Anything else names both what went wrong and — through its index — exactly which of your operations caused the rollback. In a transaction of one item that is barely useful; in a transaction of twelve it is the difference between a diagnosis and a guess.
The trap is that most logging never captures it. console.error(error) and str(e) print the message, and the message is the sentence telling you to consult reasons you did not print. Teams routinely run for months with logs full of "please refer cancellation reasons" and no way to refer to them. Logging the array is a one-line change and it is almost always the right first move.
The reason codes also split cleanly into two groups that want opposite responses. ConditionalCheckFailed is deterministic: something about the data did not match what you required, and retrying the identical transaction produces the identical cancellation. TransactionConflict and ProvisionedThroughputExceeded are transient: another writer was in the way, or capacity was momentarily short, and a retry will very likely succeed. Retrying the first wastes capacity and inflates duration; not retrying the second turns ordinary contention into a failed request. Both look the same from outside the array.
One capacity note worth carrying: a transaction consumes about twice the write capacity of the same operations issued individually, because DynamoDB performs a prepare phase and a commit phase. A table provisioned comfortably for normal traffic can start throttling when the same volume of work moves into transactions, which then surfaces here as a cancellation rather than as a throughput error you would recognise.
CloudWatch’s Errors metric: Whether CloudWatch’s Errors metric counts this depends on whether the error escaped your handler, which the log line alone does not settle. Counted only when the exception escapes the handler. A transaction cancelled by a conditional check is frequently expected control flow; one cancelled by throughput or a conflict is transient and retryable. LogStitch reports the example below as caught.
What it looks like in CloudWatch
This is the shape the failure arrives in: the lines of one invocation scattered among everything else the log group received at the same moment.
CloudWatch Logs Before
7 raw lines, in the order CloudWatch delivered them.
LogStitch After
The same lines, grouped into the invocation they belong to.
Error6d0a2f83-4c71-4e29-b508-9f2e7a1c6b30
- 15:44:18.114PLAT
START RequestId: 6d0a2f83-4c71-4e29-b508-9f2e7a1c6b30 Version: $LATEST
- 15:44:18.118INFO
INFO Committing order transaction items=2
- 15:44:18.288ERROR
ERROR TransactionCanceledException: Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None] at throwDefaultError (/var/task/node_modules/@smithy/smithy-client/dist-cjs/index.js:867:20) at commitOrder (/var/task/src/orders.js:88:11)3 lines - 15:44:18.402PLAT
END RequestId: 6d0a2f83-4c71-4e29-b508-9f2e7a1c6b30
- 15:44:18.402PLAT
REPORT RequestId: 6d0a2f83-4c71-4e29-b508-9f2e7a1c6b30 Duration: 288.44 ms Billed Duration: 289 ms Memory Size: 512 MB Max Memory Used: 108 MB Status: error
The panel on the right is generated by running the excerpt on the left through the same parser that powers the free web stitcher — it is what the tool actually produces for this input, not an illustration of it.
How to confirm it from the logs
Read the reasons array, and read it positionally. [ConditionalCheckFailed, None] means the first item in your transaction failed its condition and the second was fine. [None, None, TransactionConflict] means the third item was being written by another transaction at the same moment. The array is in the same order as the items you submitted, which is what lets you map a reason back to a specific operation.
The reason code also decides whether to retry. ConditionalCheckFailed is deterministic — retrying the identical transaction fails identically. TransactionConflict and ProvisionedThroughputExceeded are transient and usually succeed on a second attempt.
The message alone is nearly useless, and many logging setups print only the message. If your logs show the sentence without the array, the first fix is to log the array.
Causes, most likely first
One item's condition expression evaluated false
Find the ConditionalCheckFailed entry's position in the reasons array and match it to the item at that index. This behaves exactly like a standalone conditional check failure, except it also rolled back every other operation in the transaction.
Another transaction was writing the same item concurrently
Look for TransactionConflict. DynamoDB cancels a transaction when another transaction is operating on one of its items, which is contention rather than a fault, and it clears on retry.
The transaction exceeded available throughput
Look for ProvisionedThroughputExceeded or ThrottlingError in the reasons. A transaction consumes roughly twice the capacity of the equivalent non-transactional writes, because it does a prepare and a commit — so a table sized for ordinary traffic can throttle under transactional traffic at the same request rate.
The same item appears twice in one transaction
Check whether two operations in the transaction target the same key. DynamoDB rejects a transaction containing more than one operation on the same item, and this is easy to introduce when the item list is built by a loop over data with duplicates in it.
An item is larger than the limit or the transaction has too many items
Count the operations and check item sizes. A transaction is capped at 100 items and 4 MB total, and a batch assembled dynamically can cross either without anything in the code looking wrong.
Fixes
Log the cancellation reasons — the message alone does not identify anything
This is the highest-value change and it takes one line. Without the array you know only that something in a multi-item transaction failed; with it you know which item and why.
jstry {
await client.send(new TransactWriteItemsCommand({ TransactItems: items }));
} catch (error) {
if (error.name === "TransactionCanceledException") {
console.error("transaction cancelled", {
reasons: error.CancellationReasons?.map((reason, index) => ({
index,
code: reason.Code,
message: reason.Message
}))
});
}
throw error;
}
Retry only the transient reason codes
Retrying a ConditionalCheckFailed is pure waste — the condition will be false again. Retry conflicts and throughput cancellations, and let deterministic ones through to your own handling.
jsconst RETRYABLE = new Set([
"TransactionConflict",
"ProvisionedThroughputExceeded",
"ThrottlingError"
]);
function shouldRetry(error) {
return error.name === "TransactionCanceledException"
&& error.CancellationReasons?.some((reason) => RETRYABLE.has(reason.Code));
}
De-duplicate items before submitting
A transaction may not touch the same item twice. Keying the operations before submitting removes a failure that is otherwise entirely dependent on the shape of the input data.
js// Two operations on one key cancel the whole transaction.
const byKey = new Map();
for (const operation of operations) {
byKey.set(`${operation.pk}#${operation.sk}`, operation);
}
const items = [...byKey.values()];
Ask whether the operations really need to be atomic
Transactions cost roughly double the capacity and add a contention surface that individual writes do not have. Where the operations are genuinely independent, separate conditional writes are cheaper and fail in smaller, more diagnosable pieces.
Also seen as
The same underlying failure, worded differently by a different runtime, SDK version, or logging layer. All of these land here — there is no separate page for each phrasing.
Related errors
Errors that show up alongside this one, or that people mistake for it.
References
- Amazon DynamoDB Developer Guide — Handling transaction errors
- Amazon DynamoDB Developer Guide — Transaction conflict handling
LogStitch finds this automatically, across every invocation in your account.
Paste a log excerpt into the free web stitcher and see it grouped, classified, and measured in your browser — nothing is uploaded. Or run the Mac app against your own AWS profiles and get the same view over every function you own.