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

  1. Commit or rollback the active transaction before starting another on the same object
  2. Create a new transaction object per unit of work instead of reusing instances
  3. 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

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


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/cabf564f8202c819. Report an issue: GitHub.