RocketChat/Rocket.Chat · error · UnsuccessfulTransactionError
Something went wrong while trying to commit changes. Please
Error message
Something went wrong while trying to commit changes. Please try again.
What it means
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.
Source
Thrown at apps/meteor/server/database/utils.ts:84
<T extends Array<unknown>, U>(curriedCallback: (session: ClientSession) => (...args: T) => U) =>
async (...args: T): Promise<Awaited<U>> => {
const ee = new Emitter<{ success: ClientSession }>();
const extendedSession = getExtendedSession(client.startSession(), (cb) => ee.once('success', cb));
const dispatch = (session: ClientSession) => ee.emit('success', session);
try {
extendedSession.startTransaction();
extendedSession.once('ended', dispatch);
const result = await curriedCallback(extendedSession).apply(this, args);
await extendedSession.commitTransaction();
return result;
} catch (error) {
await extendedSession.abortTransaction();
extendedSession.removeListener('ended', dispatch);
if (shouldRetryTransaction(error)) {
throw new UnsuccessfulTransactionError('');
}
throw error;
} finally {
await extendedSession.endSession();
}
};
View on GitHub (pinned to b2c16d5842)
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
Example fix
// before - single shot
const result = await wrapInSessionTransaction(doStuff)(arg);
// after - retry transient transaction failures
async function runWithRetry<T>(fn: () => Promise<T>, tries = 3): Promise<T> {
for (let i = 0; i < tries; i++) {
try {
return await fn();
} catch (e) {
if ((e as Error)?.name !== 'UnsuccessfulTransactionError') throw e;
await new Promise((r) => setTimeout(r, 2 ** i * 100));
}
}
throw new Error('transaction kept failing after retries');
}
const result = await runWithRetry(() => wrapInSessionTransaction(doStuff)(arg)); Defensive patterns
Strategy: retry
Type guard
function isTransientTransactionError(e: unknown): e is Error {
return e instanceof Error && e.name === 'UnsuccessfulTransactionError';
} Try / catch
const run = wrapInSessionTransaction(doTransfer)(from, to, amount);
let lastError: unknown;
for (let attempt = 0; attempt < 3; attempt++) {
try {
const result = await run();
break; // success
} catch (e) {
if (!isTransientTransactionError(e)) throw e; // non-transient: surface it
lastError = e;
await new Promise((r) => setTimeout(r, 2 ** attempt * 100));
}
}
// handle lastError after the loop if all retries failed Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- error-room-cannot-be-closed-try-again
- error-verifying-contact-channel
- Invalid MONGO_OPTIONS environment variable: must be valid JS
- error-ai-provider-empty-response
- error-invalid-visitor
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/61fcacdd39e0d252.
Report an issue: GitHub.