t8y2/dbx · error

no manual transaction open

Error message

no manual transaction open

What it means

This error is thrown by the Oracle driver agent's commitManualTransaction when the agent holds no open manual (session-scoped) transaction — i.e. s.manualTx is nil. The manual transaction is only present after a successful 'begin manual transaction' RPC that stores the *sql.Tx in s.manualTx. Committing without one is an invalid state transition, so the agent refuses instead of panicking on a nil pointer.

Source

Thrown at agents/drivers/oracle-go/main.go:1213

	if strings.TrimSpace(schema) != "" {
		if _, err := conn.ExecContext(context.Background(), "ALTER SESSION SET CURRENT_SCHEMA = "+quoteIdentifier(schema)); err != nil {
			_ = conn.Close()
			return err
		}
	}
	tx, err := conn.BeginTx(context.Background(), nil)
	if err != nil {
		_ = conn.Close()
		return fmt.Errorf("begin manual transaction: %w", err)
	}
	s.manualConn = conn
	s.manualTx = tx
	return nil
}

func (s *server) commitManualTransaction() error {
	if s.manualTx == nil {
		return errors.New("no manual transaction open")
	}
	err := s.manualTx.Commit()
	s.clearManualTransaction()
	return err
}

func (s *server) rollbackManualTransaction() error {
	if s.manualTx == nil {
		return errors.New("no manual transaction open")
	}
	err := s.manualTx.Rollback()
	s.clearManualTransaction()
	return err
}

func (s *server) rollbackManualTransactionQuiet() error {
	if s.manualTx == nil {
		return nil

View on GitHub (pinned to c0390bff16)

Solutions

  1. Ensure a 'begin manual transaction' RPC succeeded before calling commit
  2. Remove duplicate commit calls (e.g. commit both in an error path and a defer); check hasManualTransaction first
  3. If the transaction was rolled back, do not commit — start a new transaction instead
  4. Verify the agent process was not restarted mid-transaction; re-begin the transaction if it was

Example fix

// before
await driver.commitTransaction();
await driver.commitTransaction(); // panics/errors: no manual transaction open
// after
if (await driver.hasManualTransaction()) {
  await driver.commitTransaction();
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!driver.hasManualTransaction()) {
  throw new Error('commitTransaction called without an open manual transaction')
}

Type guard

function canCommit(d) {
  return typeof d.hasManualTransaction === 'function' && d.hasManualTransaction()
}

Try / catch

try {
  await driver.commitTransaction()
} catch (e) {
  if (String(e.message).includes('no manual transaction open')) {
    // already committed/rolled back — treat as idempotent no-op
    return
  }
  throw e
}

Prevention

When it happens

Trigger: Calling the commit-transaction RPC when no manual transaction was ever begun, after it was already committed (commit clears manualTx), after it was rolled back (rollback clears manualTx), or after the agent process restarted losing its in-memory transaction state.

Common situations: Client-side double-commit due to retries; a rollback in an error path followed by a commit attempt in a defer; driver/session state lost between agent restarts while the client still thinks a transaction is open.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/f2119e2d3d1eb6b6. Report an issue: GitHub.