{"record":{"id":"9a41fcc2ddcb9a0e","repo":"gastownhall/beads","slug":"failed-to-open-ignored-tx-connection-w","errorCode":null,"errorMessage":"failed to open ignored tx connection: %w","messagePattern":"failed to open ignored tx connection: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/transaction.go","lineNumber":323,"sourceCode":"\t// fallback below, this path never switches the session's branch — see\n\t// beginBorrowedTx for the pool invariant it preserves.\n\tif conn := s.borrowConnForIgnoredTx(ctx); conn != nil {\n\t\ttx, err := beginBorrowedTx(ctx, conn, branch)\n\t\tif err == nil {\n\t\t\treturn func() { _ = conn.Close() }, tx, nil\n\t\t}\n\t\t// A stale pooled connection or a session on another branch: a fresh\n\t\t// dial always worked before, so discard this one (its session state is\n\t\t// untouched) and fall through to the fallback.\n\t\t_ = conn.Close()\n\t}\n\n\t// Fallback: a dedicated single-connection pool, paying the fresh dial the\n\t// borrow path exists to avoid.\n\tdoltMetrics.ignoredTxFreshPool.Add(ctx, 1)\n\tdb, err := sql.Open(\"mysql\", s.connStr)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed to open ignored tx connection: %w\", err)\n\t}\n\tdb.SetMaxOpenConns(1)\n\n\tconn, err := db.Conn(ctx)\n\tif err != nil {\n\t\t_ = db.Close()\n\t\treturn nil, nil, fmt.Errorf(\"failed to acquire ignored tx connection: %w\", err)\n\t}\n\n\ttx, err = beginTxOnConn(ctx, conn, branch)\n\tif err != nil {\n\t\t_ = conn.Close()\n\t\t_ = db.Close()\n\t\treturn nil, nil, err\n\t}\n\n\treturn func() { _ = conn.Close(); _ = db.Close() }, tx, nil\n}","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/transaction.go#L305-L341","documentation":"beginIgnoredTxOnBranch falls back to a dedicated single-connection pool (sql.Open(\"mysql\", s.connStr)) when no warm connection can be safely borrowed. This error wraps sql.Open failing — which for the go-sql-driver/mysql means the DSN could not be parsed or the driver is not registered, not that the network failed (dialing happens at Conn).","triggerScenarios":"Malformed or corrupted s.connStr (bad DSN parameters, unescaped characters); the mysql driver failing to register (blank import removed); DSN built dynamically with invalid host/port/params.","commonSituations":"Config or environment supplying a bad Dolt SQL DSN (e.g. spaces, bad timeout params); refactors dropping the `_ \"github.com/go-sql-driver/mysql\"` import; constructing the DSN from user input without escaping.","solutions":["Print/inspect s.connStr and validate the DSN format (user:pass@tcp(host:port)/db?params)","Ensure the mysql driver is imported: `_ \"github.com/go-sql-driver/mysql\"`","Escape DSN components built from config/env (url.QueryEscape credentials, validate host:port)","Compare against the DSN used for the main pool (which works) and diff parameters"],"exampleFix":"// before\nconnStr := fmt.Sprintf(\"%s:%s@tcp(%s)/%s\", user, pass, host, db) // pass may contain @ or :\n// after\nconnStr := fmt.Sprintf(\"%s:%s@tcp(%s)/%s\", url.QueryEscape(user), url.QueryEscape(pass), host, db)","handlingStrategy":"validation","validationCode":"if _, err := mysql.ParseDSN(connStr); err != nil { return fmt.Errorf(\"bad DSN: %w\", err) }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate the DSN once at startup with mysql.ParseDSN","Keep the `_ \"github.com/go-sql-driver/mysql\"` blank import intact","Escape credentials when building DSNs from config/env","Reuse the exact same connStr construction as the main pool"],"tags":["go","dolt","connection","dsn"],"backgroundTag":"invalid-dsn","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}