{"record":{"id":"61fcacdd39e0d252","repo":"RocketChat/Rocket.Chat","slug":"something-went-wrong-while-trying-to-commit-change","errorCode":null,"errorMessage":"Something went wrong while trying to commit changes. Please try again.","messagePattern":"Something went wrong while trying to commit changes\\. Please try again\\.","errorType":"exception","errorClass":"UnsuccessfulTransactionError","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/database/utils.ts","lineNumber":84,"sourceCode":"\t<T extends Array<unknown>, U>(curriedCallback: (session: ClientSession) => (...args: T) => U) =>\n\tasync (...args: T): Promise<Awaited<U>> => {\n\t\tconst ee = new Emitter<{ success: ClientSession }>();\n\n\t\tconst extendedSession = getExtendedSession(client.startSession(), (cb) => ee.once('success', cb));\n\n\t\tconst dispatch = (session: ClientSession) => ee.emit('success', session);\n\t\ttry {\n\t\t\textendedSession.startTransaction();\n\t\t\textendedSession.once('ended', dispatch);\n\n\t\t\tconst result = await curriedCallback(extendedSession).apply(this, args);\n\t\t\tawait extendedSession.commitTransaction();\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tawait extendedSession.abortTransaction();\n\t\t\textendedSession.removeListener('ended', dispatch);\n\t\t\tif (shouldRetryTransaction(error)) {\n\t\t\t\tthrow new UnsuccessfulTransactionError('');\n\t\t\t}\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tawait extendedSession.endSession();\n\t\t}\n\t};\n","sourceCodeStart":66,"sourceCodeEnd":91,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/database/utils.ts#L66-L91","documentation":"UnsuccessfulTransactionError is thrown by wrapInSessionTransaction (apps/meteor/server/database/utils.ts:84) when a MongoDB multi-document transaction aborts with a transient error label — errorLabels containing 'TransientTransactionCommitResult' or, per shouldRetryTransaction, 'UnknownTransactionCommitResult' or 'TransientTransactionError'. These labels mean a replica-set election, stepdown, or network blip disrupted the transaction and the outcome is unknown; the wrapper deliberately replaces the MongoError with 'Something went wrong while trying to commit changes. Please try again.' because the correct response is to re-run the entire wrapped callback, not to assume the write failed or succeeded.","triggerScenarios":"commitTransaction interrupted by a replica-set primary election or stepdown; network partition between the app and mongos/primary mid-transaction; transaction aborted by the server (e.g. exceeding transactionLifetimeLimitSeconds) in a way that carries a transient label.","commonSituations":"Replica set without a stable primary under load; slow transactions that get killed at the 60s default lifetime limit; client/mongos/driver topology changes during deploys; high-latency links between app servers and MongoDB.","solutions":["Catch errors with name 'UnsuccessfulTransactionError' and retry the whole wrapped operation from the start — the wrapper exists precisely as a retry signal","Check replica-set health (rs.status()) and confirm a stable primary exists","Shorten the transaction or raise transactionLifetimeLimitSeconds if transactions time out","Ensure the deployment supports transactions: MongoDB >= 4.0 replica set, or sharded cluster accessed via mongos"],"exampleFix":"// before - single shot\nconst result = await wrapInSessionTransaction(doStuff)(arg);\n\n// after - retry transient transaction failures\nasync function runWithRetry<T>(fn: () => Promise<T>, tries = 3): Promise<T> {\n  for (let i = 0; i < tries; i++) {\n    try {\n      return await fn();\n    } catch (e) {\n      if ((e as Error)?.name !== 'UnsuccessfulTransactionError') throw e;\n      await new Promise((r) => setTimeout(r, 2 ** i * 100));\n    }\n  }\n  throw new Error('transaction kept failing after retries');\n}\nconst result = await runWithRetry(() => wrapInSessionTransaction(doStuff)(arg));","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isTransientTransactionError(e: unknown): e is Error {\n  return e instanceof Error && e.name === 'UnsuccessfulTransactionError';\n}","tryCatchPattern":"const run = wrapInSessionTransaction(doTransfer)(from, to, amount);\nlet lastError: unknown;\nfor (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    const result = await run();\n    break; // success\n  } catch (e) {\n    if (!isTransientTransactionError(e)) throw e; // non-transient: surface it\n    lastError = e;\n    await new Promise((r) => setTimeout(r, 2 ** attempt * 100));\n  }\n}\n// handle lastError after the loop if all retries failed","preventionTips":["Make the wrapped callback idempotent — 'UnknownTransactionCommitResult' means the first attempt may have committed","Keep transactions short so they finish well inside transactionLifetimeLimitSeconds","Monitor replica-set elections/stepdowns: a burst of these errors usually points at topology churn, not your code","Never swallow the original MongoError category: only the label-checked wrapper error is retry-safe"],"tags":["mongodb","transactions","retry","replica-set"],"backgroundTag":"mongodb-transient-transaction-error","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}