t8y2/dbx · error
manual transaction already open
Error message
manual transaction already open
What it means
beginManualTransaction pins a single exclusive physical connection for manual (explicit BEGIN/COMMIT/ROLLBACK) transaction control. Only one manual transaction can be active per session because that connection is held; if s.manualTx is already non-nil, starting another would leak or corrupt the pinned connection, so it refuses.
Source
Thrown at agents/drivers/oracle-go/main.go:1183
params.URLParams = values.Encode()
return params
}
func (s *server) disconnect() error {
s.closeAllQuerySessions()
_ = s.rollbackManualTransactionQuiet()
s.legacyLOBFetchDeferred = false
if s.db == nil {
return nil
}
err := s.db.Close()
s.db = nil
return err
}
func (s *server) beginManualTransaction(schema string) error {
if s.manualTx != nil {
return errors.New("manual transaction already open")
}
db, err := s.requireDB()
if err != nil {
return err
}
// Hold one exclusive physical connection so DML/SELECT/schema stay on the
// same Oracle session for the life of the interactive transaction.
conn, err := db.Conn(context.Background())
if err != nil {
return fmt.Errorf("reserve connection for manual transaction: %w", err)
}
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)View on GitHub (pinned to c0390bff16)
Solutions
- Commit or roll back the current manual transaction before beginning a new one.
- Use separate sessions (distinct agentSessionIds) for concurrent transactions.
- On transaction failure, always issue rollback in the error path so manualTx is cleared.
- Alternatively use auto-commit/implicit transactions if nesting is not needed.
Example fix
// before
s.beginManualTransaction(schema)
if err := doWork(); err != nil { return err } // no rollback, tx stays open
// after
s.beginManualTransaction(schema)
if err := doWork(); err != nil {
s.rollbackManualTransaction()
return err
}
s.commitManualTransaction() Defensive patterns
Strategy: try-catch
Validate before calling
if s.manualTx != nil {
return errors.New("commit or rollback the open manual transaction first")
} Try / catch
if err := s.beginManualTransaction(schema); err != nil {
if err.Error() == "manual transaction already open" {
if rbErr := s.rollbackManualTransaction(); rbErr != nil { return rbErr }
return s.beginManualTransaction(schema)
}
return err
} Prevention
- Always defer rollback on the error path of a manual transaction
- Use one session per concurrent transaction; never share a session across goroutines
- Track transaction state client-side to avoid double begin
- Commit or rollback in a finally/defer block
When it happens
Trigger: Calling begin_transaction (manual mode) twice without an intervening commit or rollback on the same session.
Common situations: Error path that skipped commit/rollback before starting a new transaction, concurrent requests sharing one session, retry logic that re-begins after a failure without rolling back first.
Related errors
- cannot start a one-shot transaction while a manual transacti
- not connected
- no manual transaction open
- reserve connection for manual transaction: %w
- begin manual transaction: %w
AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05).
Data as JSON: /api/errors/7b9ae42e2044bcd2.
Report an issue: GitHub.