gastownhall/beads · error
checkout fallback branch %q: %w
Error message
checkout fallback branch %q: %w
What it means
pinStoreBranch fell back to the store's recorded branch name (s.branch) because the live SELECT active_branch() query failed, but CALL DOLT_CHECKOUT(?) on that fallback branch also failed on the fresh connection. The store must reproduce its branch checkout on a new physical connection before running branch-sensitive queries, because Dolt checkout is per-connection session state. This wrapper wraps whatever Dolt/MySQL error the checkout produced.
Source
Thrown at internal/storage/dolt/store.go:1078
// The paths that rely on this pin run effectively single-connection —
// server-mode stores are pinned to MaxOpenConns=1 precisely because branch
// isolation is session-level (see iter_issues.go) — so the read is reliable
// in practice rather than by construction. The s.branch fallback does not
// close the gap either: it fires only when the query errors, not when it
// succeeds with another connection's answer.
func (s *DoltStore) pinStoreBranch(ctx context.Context, conn execer) error {
var branch string
if scanErr := s.db.QueryRowContext(ctx, "SELECT active_branch()").Scan(&branch); scanErr == nil {
if branch != "" {
if _, err := conn.ExecContext(ctx, "CALL DOLT_CHECKOUT(?)", branch); err != nil {
return fmt.Errorf("checkout active branch %q: %w", branch, err)
}
}
} else if s.branch != "" {
// Fall back to the store's recorded branch rather than failing the
// whole call outright.
if _, err := conn.ExecContext(ctx, "CALL DOLT_CHECKOUT(?)", s.branch); err != nil {
return fmt.Errorf("checkout fallback branch %q: %w", s.branch, err)
}
}
return nil
}
// withReadTxLongTimeout is like withReadTx but runs fn against a dedicated
// one-shot connection with a 5-minute read timeout (see openLongTimeoutConn)
// instead of the shared pool's 10s ReadTimeout (see buildServerDSN). Use for
// read queries that are known to legitimately run long, e.g. dolt_history_*
// system-table scans on issues with many revisions — the pooled 10s client
// timeout otherwise surfaces as an intermittent MySQL i/o timeout / invalid
// connection error (ga-ahnxx) well before the query would have finished on
// its own. Note this only removes the client-side ceiling: the Dolt server's
// own read_timeout_millis (often configured short to bound orphaned-connection
// pileup — see the comment next to it) still applies server-side and can
// independently abort a query whose
// per-row production stalls past that window.
func (s *DoltStore) withReadTxLongTimeout(ctx context.Context, fn func(tx *sql.Tx) error) error {View on GitHub (pinned to 71377f2769)
Solutions
- Verify the recorded branch still exists: run CALL DOLT_BRANCHES() or `dolt branch` and re-create it if missing, or re-point the store at an existing branch via Checkout().
- Reopen/reinitialize the DoltStore so s.branch is refreshed from live session state instead of a stale value.
- Check connectivity to the Dolt sql-server and confirm the server is healthy; the underlying wrapped error usually names the concrete cause (unknown branch vs connection failure).
- If the database uses per-test branches, ensure branch setup runs before any store operation that opens long-timeout connections.
Example fix
// before: store pinned to a deleted branch
store, _ := OpenDoltStore(ctx, dbPath)
_ = store.Checkout(ctx, "feature/x") // branch later deleted externally
// after: verify branch before checkout, or checkout an existing one
if !branchExists(db, "feature/x") {
_ = store.Checkout(ctx, "main")
} Defensive patterns
Strategy: try-catch
Validate before calling
// verify the recorded branch exists before store operations
rows, err := db.Query("CALL DOLT_BRANCHES()")
if err != nil { return err }
defer rows.Close()
found := false
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil { return err }
if name == wantBranch { found = true }
}
if !found { return fmt.Errorf("branch %q missing; re-checkout main", wantBranch) } Try / catch
err := store.QueryX(ctx, ...)
var derr *storage.DoltError
if errors.As(err, &derr) && strings.Contains(derr.Error(), "checkout fallback branch") {
// branch is gone: re-create or re-checkout, then retry once
_ = store.Checkout(ctx, "main")
err = store.QueryX(ctx, ...)
} Prevention
- Do not delete branches that a running store is checked out to; merge and delete only after re-checkout.
- Re-checkout 'main' (or a long-lived branch) at the end of per-test branch isolation setups.
- Keep the Dolt server healthy; most checkout failures are connection failures in disguise.
When it happens
Trigger: A dedicated one-shot connection (openLongTimeoutConn) was opened, the pooled session could not answer SELECT active_branch(), and CALL DOLT_CHECKOUT on the recorded branch failed — e.g. the branch no longer exists, the connection died before checkout, or the server rejected the session command.
Common situations: Branch was deleted (or merged and removed) after the store recorded it; stale s.branch pointing at a dropped worktree; Dolt server restarted or connection pool churn; a test harness created per-test branches that were torn down before the store finished using them.
Related errors
- ErrExec
- database not available: %w
- not using Dolt backend (configured backend %q)
- no storage backend is open
- storage backend does not support backup operations
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/9fc5b6f6efdddbd9.
Report an issue: GitHub.