{"record":{"id":"32fa8fe223d5e625","repo":"mongodb/node-mongodb-native","slug":"function-provided-to-withtransaction-must-return","errorCode":null,"errorMessage":"Function provided to `withTransaction` must return a Promise","messagePattern":"Function provided to `withTransaction` must return a Promise","errorType":"exception","errorClass":"MongoInvalidArgumentError","httpStatus":null,"severity":"error","filePath":"src/sessions.ts","lineNumber":819,"sourceCode":"          }\n\n          await setTimeout(backoffMS);\n        }\n\n        // 3. Invoke startTransaction on the session and increment transactionAttempt. If TransactionOptions were\n        // specified in the call to withTransaction, those MUST be used for startTransaction. Note that\n        // ClientSession.defaultTransactionOptions will be used in the absence of any explicit TransactionOptions.\n        // 4. If startTransaction reported an error, propagate that error to the caller of withTransaction as is and\n        // return immediately.\n        this.startTransaction(options);\n\n        try {\n          // 5. Invoke the callback. Drivers MUST ensure that the ClientSession can be accessed within the callback\n          // (e.g. pass ClientSession as the first parameter, rely on lexical scoping). Drivers MAY pass additional\n          // parameters as needed (e.g. user data solicited by withTransaction).\n          const promise = fn(this);\n          if (!isPromiseLike(promise)) {\n            throw new MongoInvalidArgumentError(\n              'Function provided to `withTransaction` must return a Promise'\n            );\n          }\n\n          // 6. Control returns to withTransaction. Determine the current state of the ClientSession and whether the\n          // callback reported an error (e.g. thrown exception, error output parameter).\n          result = await promise;\n\n          // 8. If the ClientSession is in the \"no transaction\", \"transaction aborted\", or \"transaction committed\"\n          // state, assume the callback intentionally aborted or committed the transaction and return immediately.\n          if (\n            this.transaction.state === TxnState.NO_TRANSACTION ||\n            this.transaction.state === TxnState.TRANSACTION_COMMITTED ||\n            this.transaction.state === TxnState.TRANSACTION_ABORTED\n          ) {\n            return result;\n          }\n        } catch (fnError) {","sourceCodeStart":801,"sourceCodeEnd":837,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/dce7939f86fb283e167ad709955abedb7bf23124/src/sessions.ts#L801-L837","documentation":"`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.","triggerScenarios":"`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.","commonSituations":"Forgetting `async` on the callback; calling an async function without `return await`/`return`; callback written synchronously by mistake.","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); }`."],"exampleFix":"// before\nawait session.withTransaction((s) => {\n  collection.insertOne(doc, { session: s });\n});\n\n// after\nawait session.withTransaction(async (s) => {\n  await collection.insertOne(doc, { session: s });\n});","handlingStrategy":"type-guard","validationCode":"function isPromise(v) {\n  return v != null && typeof v.then === 'function';\n}\nasync function runWithTxn(session, fn) {\n  return session.withTransaction(async (s) => fn(s));\n}","typeGuard":"function returnsPromise(fn) {\n  // best-effaint static check: callbacks passed to withTransaction should be async\n  return fn.constructor?.name === 'AsyncFunction';\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["sessions","transactions","validation","async","common-mistake"],"backgroundTag":null,"analyzedSha":"dce7939f86fb283e167ad709955abedb7bf23124","analyzedAt":"2026-08-11T04:54:53.215Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}