ruvnet/ruflo · error
Transaction already active
Error message
Transaction already active
What it means
Thrown by the transaction wrapper's begin() when this.isActive is already true. The wrapper binds one pg client to one transaction lifecycle; issuing BEGIN twice on the same client without an intervening commit/rollback would nest transactions illegally in Postgres, so the second begin() is rejected.
Source
Thrown at v3/@claude-flow/plugins/src/integrations/ruvector/streaming.ts:860
) {
super();
this.client = client;
this.schema = options.schema;
this.defaultTableName = options.defaultTableName ?? 'vectors';
}
// ===========================================================================
// Transaction Control
// ===========================================================================
/**
* Begin a transaction with optional isolation level.
*
* @param isolation - Transaction isolation level
*/
async begin(isolation?: IsolationLevel): Promise<void> {
if (this.isActive) {
throw new Error('Transaction already active');
}
this.transactionId = `tx_${Date.now()}_${Math.random().toString(36).slice(2)}`;
this.startTime = Date.now();
let sql = 'BEGIN';
if (isolation) {
sql += ` ISOLATION LEVEL ${isolation.replace('_', ' ').toUpperCase()}`;
}
await this.client.query(sql);
this.isActive = true;
this.queryCount = 1;
this.emit('begin', { transactionId: this.transactionId, isolation });
}
/**View on GitHub (pinned to fa13ee4ad6)
Solutions
- Commit or rollback the active transaction before starting another on the same object
- Create a new transaction object per unit of work instead of reusing instances
- Guard with the wrapper's isActive (or a hasActiveTransaction helper) before calling begin()
Example fix
// before await tx.begin(); await tx.begin(); // throws // after await tx.begin(); await tx.commit(); await tx.begin();
Defensive patterns
Strategy: validation
Validate before calling
if (tx.isActive) {
await tx.rollback(); // or commit, depending on outcome
}
await tx.begin(isolation); Type guard
const hasActiveTransaction = (t: { isActive: boolean }): boolean =>
t.isActive === true; Try / catch
try {
await tx.begin();
} catch (err) {
if (err instanceof Error && err.message === 'Transaction already active') {
await tx.rollback(); // clean slate, then retry
await tx.begin();
} else {
throw err;
}
} Prevention
- Use one transaction object per unit of work; create fresh rather than reusing
- Always pair begin() with commit()/rollback() in try/finally so isActive cannot leak true
- Keep begin() in exactly one layer (the withTransaction helper), never in both helper and caller
When it happens
Trigger: A helper and its caller both calling begin() on the same transaction object; reusing a transaction instance across loop iterations without commit/rollback; retry logic that re-issues begin() after a statement error while the transaction is still open.
Common situations: Extracting a 'withTransaction' utility that begins internally while callers also begin; batch ingestion loops reusing one transaction for multiple batches; error paths that abort before rollback and then retry begin().
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- File not found
- Connection pool already initialized
- Failed to initialize connection pool: ${(error as Error).mes
- pg (node-postgres) package not found. Install it with: npm i
- Savepoint '${name}' does not exist
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/cabf564f8202c819.
Report an issue: GitHub.