mongodb/node-mongodb-native · error · MongoInvalidArgumentError
Function provided to `withTransaction` must return a Promise
Error message
Function provided to `withTransaction` must return a Promise
What it means
`withTransaction` requires its callback to return a thenable so the driver can await it, track errors, and retry. After invoking the callback the driver runs `isPromiseLike(promise)`; if it is not a Promise it throws MongoInvalidArgumentError (sessions.ts:818). This is enforced because synchronous callbacks cannot propagate transactional errors correctly.
Solutions
- Mark the callback `async`: `session.withTransaction(async (s) => { ... })`.
- Ensure every async operation inside is awaited (or returned) so the callback returns a Promise.
- If your helper builds the callback dynamically, wrap a sync function with `async (s) => { fn(s); }`.
Example fix
// before
await session.withTransaction((s) => {
collection.insertOne(doc, { session: s });
});
// after
await session.withTransaction(async (s) => {
await collection.insertOne(doc, { session: s });
}); Defensive patterns
Strategy: type-guard
Validate before calling
function isPromise(v) {
return v != null && typeof v.then === 'function';
}
async function runWithTxn(session, fn) {
return session.withTransaction(async (s) => fn(s));
} Type guard
function returnsPromise(fn) {
// best-effaint static check: callbacks passed to withTransaction should be async
return fn.constructor?.name === 'AsyncFunction';
} Prevention
- Always declare the withTransaction callback `async`.
- Await (or return) every async operation inside the callback.
- Add a lint rule or code review check that withTransaction callbacks are async functions.
When it happens
Trigger: `session.withTransaction((s) => { collection.insertOne(doc, { session: s }); })` — the arrow function returns undefined (no `async`, no `return`). Also a callback that returns a plain value instead of a Promise.
Common situations: Forgetting `async` on the callback; calling an async function without `return await`/`return`; callback written synchronously by mistake.
Related errors
- Cannot call abortTransaction after calling commitTransaction
- Cannot call abortTransaction twice
- Cannot call commitTransaction after calling abortTransaction
- No transaction started
- Transaction already in progress
AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11).
Data as JSON: /api/errors/32fa8fe223d5e625.
Report an issue: GitHub.
Appendix: source
Thrown at src/sessions.ts:819
}
await setTimeout(backoffMS);
}
// 3. Invoke startTransaction on the session and increment transactionAttempt. If TransactionOptions were
// specified in the call to withTransaction, those MUST be used for startTransaction. Note that
// ClientSession.defaultTransactionOptions will be used in the absence of any explicit TransactionOptions.
// 4. If startTransaction reported an error, propagate that error to the caller of withTransaction as is and
// return immediately.
this.startTransaction(options);
try {
// 5. Invoke the callback. Drivers MUST ensure that the ClientSession can be accessed within the callback
// (e.g. pass ClientSession as the first parameter, rely on lexical scoping). Drivers MAY pass additional
// parameters as needed (e.g. user data solicited by withTransaction).
const promise = fn(this);
if (!isPromiseLike(promise)) {
throw new MongoInvalidArgumentError(
'Function provided to `withTransaction` must return a Promise'
);
}
// 6. Control returns to withTransaction. Determine the current state of the ClientSession and whether the
// callback reported an error (e.g. thrown exception, error output parameter).
result = await promise;
// 8. If the ClientSession is in the "no transaction", "transaction aborted", or "transaction committed"
// state, assume the callback intentionally aborted or committed the transaction and return immediately.
if (
this.transaction.state === TxnState.NO_TRANSACTION ||
this.transaction.state === TxnState.TRANSACTION_COMMITTED ||
this.transaction.state === TxnState.TRANSACTION_ABORTED
) {
return result;
}
} catch (fnError) {View on GitHub (pinned to dce7939f86)