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 nilView on GitHub (pinned to c0390bff16)
Solutions
- Ensure a 'begin manual transaction' RPC succeeded before calling commit
- Remove duplicate commit calls (e.g. commit both in an error path and a defer); check hasManualTransaction first
- If the transaction was rolled back, do not commit — start a new transaction instead
- 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
- Wrap begin/commit/rollback in a single client-side helper that tracks state
- Never commit twice; use a once-style guard for cleanup paths
- Log transaction lifecycle transitions to spot double-commit
- Re-verify agent session state after any agent restart
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
- cannot start a one-shot transaction while a manual transacti
- manual transaction already open
- reserve connection for manual transaction: %w
- begin manual transaction: %w
- Query timeout cannot be negative: " + timeoutSecs
AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05).
Data as JSON: /api/errors/f2119e2d3d1eb6b6.
Report an issue: GitHub.