{"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/3366c21a6311e02f1be91da982f9b93d3cce99a0/src/sessions.ts#L801-L837","documentation":"Thrown by ClientSession.withTransaction() after invoking the user callback: if the callback's return value is not a Promise (thenable), the driver cannot await it and cannot sequence commit/retry. withTransaction relies on the callback returning a promise that propagates operation rejections. It is a MongoInvalidArgumentError.","triggerScenarios":"Passing a synchronous (non-async) function to withTransaction that returns undefined or a plain value; a callback that forgets `return` on its async operations; an arrow function that does work but returns nothing.","commonSituations":"Forgetting the `async` keyword on the withTransaction callback; callback that calls awaitable ops without returning them; refactoring an async function into a sync one.","solutions":["Make the callback async: await client.withTransaction(async (session) => { ... }).","Ensure every async operation inside is awaited and the final promise is returned (in an async function, awaited ops propagate automatically).","If using a non-async arrow, explicitly return a Promise: (session) => Promise.resolve(...)."],"exampleFix":"// before\nawait session.withTransaction((session) => {\n  coll.insertOne(doc, { session }); // returns undefined, not a promise -> throws\n});\n\n// after\nawait session.withTransaction(async (session) => {\n  await coll.insertOne(doc, { session });\n});","handlingStrategy":"type-guard","validationCode":"function isPromiseLike<T>(v: unknown): v is PromiseLike<T> {\n  return v != null && typeof (v as any).then === 'function';\n}\nconst cb = (session) => { /* ... */ };\nif (isPromiseLike(cb(session))) {\n  // safe to pass to withTransaction shape\n}","typeGuard":"function isAsyncFn(fn: unknown): fn is (...args: any[]) => Promise<any> {\n  return typeof fn === 'function' && fn.constructor?.name === 'AsyncFunction';\n}","tryCatchPattern":"try {\n  await session.withTransaction(fn);\n} catch (e) {\n  if (e instanceof MongoInvalidArgumentError && /must return a Promise/.test(e.message)) {\n    // wrap fn in async and retry: await session.withTransaction(async (s) => fn(s))\n  } else throw e;\n}","preventionTips":["Always declare the withTransaction callback as async.","Ensure every awaited op inside is actually awaited so the promise propagates.","Add a lint rule or review step that flags non-async callbacks passed to withTransaction."],"tags":["sessions","transactions","withtransaction","invalid-argument","async"],"analyzedSha":"3366c21a6311e02f1be91da982f9b93d3cce99a0","analyzedAt":"2026-08-04T13:40:15.335Z","schemaVersion":2}