TransactionCanceledException: Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None]

Short answer

DynamoDB rolled the whole transaction back because one of its operations could not proceed. The useful information is not in the message — it is in the cancellation reasons array, positional by item, and the position tells you which operation was responsible.

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.

Depends

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.

2026-08-30T15:44:18.114Z START RequestId: 6d0a2f83-4c71-4e29-b508-9f2e7a1c6b30 Version: $LATEST
2026-08-30T15:44:18.118Z 6d0a2f83-4c71-4e29-b508-9f2e7a1c6b30 INFO Committing order transaction items=2
2026-08-30T15:44:18.288Z 6d0a2f83-4c71-4e29-b508-9f2e7a1c6b30 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)
2026-08-30T15:44:18.402Z END RequestId: 6d0a2f83-4c71-4e29-b508-9f2e7a1c6b30
2026-08-30T15:44:18.402Z 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

LogStitch After

The same lines, grouped into the invocation they belong to.

Error6d0a2f83-4c71-4e29-b508-9f2e7a1c6b30dur 288msbilled 289msmem 108/512MBlogs 5
  1. 15:44:18.114PLAT
    START RequestId: 6d0a2f83-4c71-4e29-b508-9f2e7a1c6b30 Version: $LATEST
  2. 15:44:18.118INFO
    INFO	Committing order transaction items=2
  3. 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
  4. 15:44:18.402PLAT
    END RequestId: 6d0a2f83-4c71-4e29-b508-9f2e7a1c6b30
  5. 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
Error
Status
288ms
Duration
289ms
Billed
108/512MB
Memory
79%
Headroom
No
Cold start
LogLevelError
Error type

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

1

One item's condition expression evaluated false

How to confirm

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.

2

Another transaction was writing the same item concurrently

How to confirm

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.

3

The transaction exceeded available throughput

How to confirm

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.

4

The same item appears twice in one transaction

How to confirm

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.

5

An item is larger than the limit or the transaction has too many items

How to confirm

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

Fix 1

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;
}
Fix 2

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));
}
Fix 3

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()];
Fix 4

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.

Transaction cancelled, please refer cancellation reasons for specific reasons[None, ConditionalCheckFailed]TransactionConflictdynamodb transaction cancelled lambda

Errors that show up alongside this one, or that people mistake for it.

References

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.