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

  1. 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().
  2. Reopen/reinitialize the DoltStore so s.branch is refreshed from live session state instead of a stale value.
  3. 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).
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/9fc5b6f6efdddbd9. Report an issue: GitHub.