gastownhall/beads · warning
failed to read borrowed conn's active branch: %w
Error message
failed to read borrowed conn's active branch: %w
What it means
beginBorrowedTx first queries `SELECT active_branch()` on the borrowed pooled connection to confirm it is already on the regular transaction's branch. This error wraps that read failing — a stale/dead pooled session, ctx cancellation (including the 250ms ignoredTxBorrowTimeout parent), or a non-Dolt server. On error the borrow path falls back to a fresh dial, so this is usually transient.
Source
Thrown at internal/storage/dolt/transaction.go:390
//
// Pool invariant: DOLT_CHECKOUT is session-level, and the borrow cleanup
// returns the connection to the pool as-is — so switching its branch here
// would leak a foreign branch into the pool for an unrelated later caller.
// Every other production checkout site (federation staging, compact, flatten)
// restores the branch before releasing the connection; the borrow path
// preserves the same invariant by refusing instead of switching. Today no
// shipped flow diverges a pool session's branch from the regular tx's branch
// (DoltStore.Checkout has no non-test callers), so this is defense in depth
// for future Checkout callers and for multi-connection tests.
//
// Instead of an unconditional checkout it verifies the session is already on
// the requested branch — the overwhelmingly common case — and sends the
// caller to the fresh-dial fallback otherwise. Same round-trip count as the
// checkout it replaces (one statement), so the borrow fast path stays free.
func beginBorrowedTx(ctx context.Context, conn *sql.Conn, branch string) (*sql.Tx, error) {
var active string
if err := conn.QueryRowContext(ctx, "SELECT active_branch()").Scan(&active); err != nil {
return nil, fmt.Errorf("failed to read borrowed conn's active branch: %w", err)
}
if active != branch {
return nil, fmt.Errorf("borrowed conn is on branch %q, want %q: refusing to switch a pooled session's branch", active, branch)
}
tx, err := conn.BeginTx(ctx, nil)
if err != nil {
return nil, fmt.Errorf("failed to begin ignored tx: %w", err)
}
return tx, nil
}
// beginTxOnConn checks a connection out to branch and begins a transaction on
// it. Only the fallback path uses it: the fallback owns a dedicated
// single-connection pool, so checking its session out is safe. Every Dolt SQL
// session has its own active branch, so the explicit checkout is required on
// a fresh dial.
func beginTxOnConn(ctx context.Context, conn *sql.Conn, branch string) (*sql.Tx, error) {
if _, err := conn.ExecContext(ctx, "CALL DOLT_CHECKOUT(?)", branch); err != nil {View on GitHub (pinned to 71377f2769)
Solutions
- Retry — the code automatically discards the borrowed conn and retries via a fresh-dial fallback, so this only surfaces if you wrap/observe it directly
- Tune server idle timeouts (wait_timeout) above your busiest transaction duration
- Check ctx deadlines; widen them if the 250ms borrow timeout or parent deadline is expiring
- Enable pool health checks / connection lifetimes (ConnMaxLifetime) shorter than server idle timeout
Example fix
// before db.SetConnMaxLifetime(0) // sessions can idle past server wait_timeout and die // after db.SetConnMaxLifetime(30 * time.Second) // recycle before server drops idle sessions
Defensive patterns
Strategy: fallback
Validate before calling
if err := db.PingContext(ctx); err != nil { /* recycle pool before writes */ } Try / catch
// the library already falls back to a fresh dial; when observing this error:
if err != nil && strings.Contains(err.Error(), "failed to read borrowed conn's active branch") {
log.Warn("stale borrowed session; fresh-dial fallback engaged")
} Prevention
- Set ConnMaxLifetime shorter than the server's idle/wait_timeout
- Recycle or Ping the pool after any Dolt server restart
- Avoid propagating tiny parent deadlines into transaction setup
- Monitor dropped-connection metrics from load balancers/gateways
When it happens
Trigger: The borrowed pooled connection died since it was parked (server restart, wait_timeout); ctx canceled during the query; server degraded so even trivial queries fail.
Common situations: Idle pools left open across server restarts; hosted-gateway load balancers dropping idle sessions; very short deadlines propagating into transaction setup.
Related errors
- acquire connection for gc: %w
- acquire connection for remote-ref prune: %w
- acquire connection for flatten: %w
- acquire connection for compact: %w
- failed to acquire connection: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/8990f21fd3f49775.
Report an issue: GitHub.