{"record":{"id":"a4876cbf38cbe50b","repo":"gastownhall/beads","slug":"begin-read-tx-w","errorCode":null,"errorMessage":"begin read tx: %w","messagePattern":"begin read tx: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/store.go","lineNumber":1025,"sourceCode":"\n// withReadTx runs fn inside a transaction while holding the store's read-lock.\n// Used for read operations that need a *sql.Tx to share issueops functions.\n//\n// The whole BeginTx+fn is wrapped in withRetry so a transient connection error\n// (e.g. \"invalid connection\" when the dolt sql-server reaps a pooled connection\n// that has been idle past its wait_timeout) is retried rather than surfaced to\n// the caller. This is safe because fn is read-only and the transaction is always\n// rolled back, so re-running the operation has no side effects.\nfunc (s *DoltStore) withReadTx(ctx context.Context, fn func(tx *sql.Tx) error) error {\n\tif s.closed.Load() {\n\t\treturn ErrStoreClosed\n\t}\n\ts.mu.RLock()\n\tdefer s.mu.RUnlock()\n\treturn s.withRetry(ctx, func() error {\n\t\ttx, err := s.db.BeginTx(ctx, nil)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"begin read tx: %w\", err)\n\t\t}\n\t\tdefer func() { _ = tx.Rollback() }()\n\t\treturn fn(tx)\n\t})\n}\n\n// execer is satisfied by both *sql.DB and *sql.Conn, letting pinStoreBranch\n// share one implementation between withReadTxLongTimeout's one-shot *sql.DB\n// and a single pinned *sql.Conn (see recomputeAllBlocked/recomputeBlockedTx).\ntype execer interface {\n\tExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)\n}\n\n// pinStoreBranch reproduces the store's real active branch on conn. Branch\n// checkout is Dolt session state, scoped to one physical connection — a\n// fresh connection (from openLongTimeoutConn or db.Conn) defaults to the\n// database's default branch rather than inheriting whatever branch the\n// store's pooled session (s.db) is actually checked out to. Query s.db for","sourceCodeStart":1007,"sourceCodeEnd":1043,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/store.go#L1007-L1043","documentation":"A read-only transaction could not be started on the Dolt database inside withRetry; BeginTx returned a driver error wrapped as 'begin read tx'. This powers the store's read path, so it means the database was unavailable at that moment; the withRetry wrapper may already have retried transient failures.","triggerScenarios":"Any read-API call (via this readTx helper) when BeginTx fails: connection closed, pool exhausted, context cancelled/timed out, embedded Dolt server unreachable or locked.","commonSituations":"Store used after Close; context deadline too short under load; stale Dolt lock; embedded server killed by OOM; too many concurrent readers exhausting the pool.","solutions":["Check database availability with bd doctor; clear stale locks with bd doctor --fix.","Increase the context timeout or retry with a fresh context.","If the store was closed, recreate/reopen the DoltStore.","Reduce concurrent reader pressure or enlarge the connection pool."],"exampleFix":"// before\nctx, cancel := context.WithTimeout(ctx, 50*time.Millisecond) // too short under load\nissues, err := store.GetIssues(ctx)\n// after\nctx, cancel := context.WithTimeout(ctx, 5*time.Second)\nissues, err := store.GetIssues(ctx)","handlingStrategy":"retry","validationCode":"if err := ctx.Err(); err != nil {\n\treturn err\n}\nif err := store.Ping(ctx); err != nil {\n\treturn fmt.Errorf(\"database unreachable: %w\", err)\n}","typeGuard":"func isBeginTxError(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"begin read tx\")\n}","tryCatchPattern":"err := store.GetIssues(ctx)\nif err != nil && isBeginTxError(err) {\n\ttime.Sleep(100 * time.Millisecond)\n\terr = store.GetIssues(ctx) // withRetry may have exhausted its attempts\n}","preventionTips":["Give read operations generous context timeouts under load.","Keep the store open for the process lifetime; reopen if it was closed.","Run bd doctor --fix to clear stale locks after crashes.","Cap concurrent readers or enlarge the SQL connection pool."],"tags":["database","transaction","dolt","connection"],"backgroundTag":"begin-transaction-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}