tursodatabase/turso · error · Error

No active remote transaction

Error message

No active remote transaction

What it means

RemoteWriter.commitTransaction() requires an open session created by beginTransaction(); the session field doubles as the in-remote-transaction flag. If commit is attempted with no session — no BEGIN sent, or a previous COMMIT/ROLLBACK already closed and nulled the session — it throws immediately without touching the network. The finally block guarantees the session is always cleared after the first commit/rollback, which is why double-commit hits this path.

Source

Thrown at bindings/javascript/sync/packages/common/remote-writer.ts:90

            await session.close();
        }
    }

    /**
     * Begin a remote transaction. Creates a session and sends BEGIN.
     */
    async beginTransaction(mode: string): Promise<void> {
        this.session = await this.createSession();
        await this.session.sequence("BEGIN " + mode);
        this._inRemoteTxn = true;
    }

    /**
     * Commit the remote transaction. Sends COMMIT and closes the session.
     */
    async commitTransaction(): Promise<void> {
        if (!this.session) {
            throw new Error("No active remote transaction");
        }
        try {
            await this.session.sequence("COMMIT");
        } finally {
            this._inRemoteTxn = false;
            await this.session.close();
            this.session = null;
        }
    }

    /**
     * Rollback the remote transaction. Sends ROLLBACK and closes the session.
     */
    async rollbackTransaction(): Promise<void> {
        if (!this.session) {
            throw new Error("No active remote transaction");
        }
        try {

View on GitHub (pinned to bad083fafb)

Solutions

  1. Only commit after a successful beginTransaction(); check writer.isInTransaction first.
  2. In finally blocks, guard the commit: if (!writer.isInTransaction) skip it.
  3. Make BEGIN failures abort before the callback runs, so COMMIT is never attempted.
  4. Serialize access so only one code path ends a given remote transaction.

Example fix

// before
await writer.beginTransaction('DEFERRED');
try { await doWork(); } finally { await writer.commitTransaction(); } // BEGIN failure -> commit throws too

// after
await writer.beginTransaction('DEFERRED');
let ok = false;
try { await doWork(); ok = true; }
finally {
  if (ok) await writer.commitTransaction();
  else await writer.rollbackTransaction().catch(() => {});
}
Defensive patterns

Strategy: validation

Validate before calling

if (!writer.isInTransaction) {
  throw new Error('cannot commit: BEGIN never sent or session already closed');
}
await writer.commitTransaction();

Try / catch

try { await writer.commitTransaction(); } catch (e) { if (e instanceof Error && e.message === 'No active remote transaction') { /* already ended; treat as no-op */ } else throw e; }

Prevention

When it happens

Trigger: Calling writer.commitTransaction() before any beginTransaction(); calling commit twice (the second has session === null); beginning failed midway so the session was never assigned; calling rollback after commit.

Common situations: Transaction wrappers that always issue COMMIT in a finally block even when BEGIN threw; retry logic that replays COMMIT; parallel flows both trying to end the same remote transaction; error paths where BEGIN failed on the server.

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/6ca581a10a14fc15. Report an issue: GitHub.