{"record":{"id":"9fc5b6f6efdddbd9","repo":"gastownhall/beads","slug":"checkout-fallback-branch-q-w","errorCode":null,"errorMessage":"checkout fallback branch %q: %w","messagePattern":"checkout fallback branch %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/store.go","lineNumber":1078,"sourceCode":"// The paths that rely on this pin run effectively single-connection —\n// server-mode stores are pinned to MaxOpenConns=1 precisely because branch\n// isolation is session-level (see iter_issues.go) — so the read is reliable\n// in practice rather than by construction. The s.branch fallback does not\n// close the gap either: it fires only when the query errors, not when it\n// succeeds with another connection's answer.\nfunc (s *DoltStore) pinStoreBranch(ctx context.Context, conn execer) error {\n\tvar branch string\n\tif scanErr := s.db.QueryRowContext(ctx, \"SELECT active_branch()\").Scan(&branch); scanErr == nil {\n\t\tif branch != \"\" {\n\t\t\tif _, err := conn.ExecContext(ctx, \"CALL DOLT_CHECKOUT(?)\", branch); err != nil {\n\t\t\t\treturn fmt.Errorf(\"checkout active branch %q: %w\", branch, err)\n\t\t\t}\n\t\t}\n\t} else if s.branch != \"\" {\n\t\t// Fall back to the store's recorded branch rather than failing the\n\t\t// whole call outright.\n\t\tif _, err := conn.ExecContext(ctx, \"CALL DOLT_CHECKOUT(?)\", s.branch); err != nil {\n\t\t\treturn fmt.Errorf(\"checkout fallback branch %q: %w\", s.branch, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// withReadTxLongTimeout is like withReadTx but runs fn against a dedicated\n// one-shot connection with a 5-minute read timeout (see openLongTimeoutConn)\n// instead of the shared pool's 10s ReadTimeout (see buildServerDSN). Use for\n// read queries that are known to legitimately run long, e.g. dolt_history_*\n// system-table scans on issues with many revisions — the pooled 10s client\n// timeout otherwise surfaces as an intermittent MySQL i/o timeout / invalid\n// connection error (ga-ahnxx) well before the query would have finished on\n// its own. Note this only removes the client-side ceiling: the Dolt server's\n// own read_timeout_millis (often configured short to bound orphaned-connection\n// pileup — see the comment next to it) still applies server-side and can\n// independently abort a query whose\n// per-row production stalls past that window.\nfunc (s *DoltStore) withReadTxLongTimeout(ctx context.Context, fn func(tx *sql.Tx) error) error {","sourceCodeStart":1060,"sourceCodeEnd":1096,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/store.go#L1060-L1096","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: store pinned to a deleted branch\nstore, _ := OpenDoltStore(ctx, dbPath)\n_ = store.Checkout(ctx, \"feature/x\") // branch later deleted externally\n// after: verify branch before checkout, or checkout an existing one\nif !branchExists(db, \"feature/x\") {\n    _ = store.Checkout(ctx, \"main\")\n}","handlingStrategy":"try-catch","validationCode":"// verify the recorded branch exists before store operations\nrows, err := db.Query(\"CALL DOLT_BRANCHES()\")\nif err != nil { return err }\ndefer rows.Close()\nfound := false\nfor rows.Next() {\n    var name string\n    if err := rows.Scan(&name); err != nil { return err }\n    if name == wantBranch { found = true }\n}\nif !found { return fmt.Errorf(\"branch %q missing; re-checkout main\", wantBranch) }","typeGuard":null,"tryCatchPattern":"err := store.QueryX(ctx, ...)\nvar derr *storage.DoltError\nif errors.As(err, &derr) && strings.Contains(derr.Error(), \"checkout fallback branch\") {\n    // branch is gone: re-create or re-checkout, then retry once\n    _ = store.Checkout(ctx, \"main\")\n    err = store.QueryX(ctx, ...)\n}","preventionTips":["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."],"tags":["dolt","branch","checkout","storage"],"backgroundTag":"dolt-branch-checkout-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}