{"record":{"id":"7b9ae42e2044bcd2","repo":"t8y2/dbx","slug":"manual-transaction-already-open","errorCode":null,"errorMessage":"manual transaction already open","messagePattern":"manual transaction already open","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agents/drivers/oracle-go/main.go","lineNumber":1183,"sourceCode":"\tparams.URLParams = values.Encode()\n\treturn params\n}\n\nfunc (s *server) disconnect() error {\n\ts.closeAllQuerySessions()\n\t_ = s.rollbackManualTransactionQuiet()\n\ts.legacyLOBFetchDeferred = false\n\tif s.db == nil {\n\t\treturn nil\n\t}\n\terr := s.db.Close()\n\ts.db = nil\n\treturn err\n}\n\nfunc (s *server) beginManualTransaction(schema string) error {\n\tif s.manualTx != nil {\n\t\treturn errors.New(\"manual transaction already open\")\n\t}\n\tdb, err := s.requireDB()\n\tif err != nil {\n\t\treturn err\n\t}\n\t// Hold one exclusive physical connection so DML/SELECT/schema stay on the\n\t// same Oracle session for the life of the interactive transaction.\n\tconn, err := db.Conn(context.Background())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"reserve connection for manual transaction: %w\", err)\n\t}\n\tif strings.TrimSpace(schema) != \"\" {\n\t\tif _, err := conn.ExecContext(context.Background(), \"ALTER SESSION SET CURRENT_SCHEMA = \"+quoteIdentifier(schema)); err != nil {\n\t\t\t_ = conn.Close()\n\t\t\treturn err\n\t\t}\n\t}\n\ttx, err := conn.BeginTx(context.Background(), nil)","sourceCodeStart":1165,"sourceCodeEnd":1201,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/oracle-go/main.go#L1165-L1201","documentation":"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.","triggerScenarios":"Calling begin_transaction (manual mode) twice without an intervening commit or rollback on the same session.","commonSituations":"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.","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."],"exampleFix":"// before\ns.beginManualTransaction(schema)\nif err := doWork(); err != nil { return err } // no rollback, tx stays open\n// after\ns.beginManualTransaction(schema)\nif err := doWork(); err != nil {\n    s.rollbackManualTransaction()\n    return err\n}\ns.commitManualTransaction()","handlingStrategy":"try-catch","validationCode":"if s.manualTx != nil {\n    return errors.New(\"commit or rollback the open manual transaction first\")\n}","typeGuard":null,"tryCatchPattern":"if err := s.beginManualTransaction(schema); err != nil {\n    if err.Error() == \"manual transaction already open\" {\n        if rbErr := s.rollbackManualTransaction(); rbErr != nil { return rbErr }\n        return s.beginManualTransaction(schema)\n    }\n    return err\n}","preventionTips":["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"],"tags":["oracle","transaction","state","concurrency"],"backgroundTag":"transaction-already-open","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}