{"record":{"id":"e4579778e40b2057","repo":"gastownhall/beads","slug":"errstoreclosed","errorCode":"ErrStoreClosed","errorMessage":"store is closed","messagePattern":"store is closed","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/store.go","lineNumber":1006,"sourceCode":"\t\treturn q[:300] + \"…\"\n\t}\n\treturn q\n}\n\n// endSpan records an error (if any) and ends the span.\nfunc endSpan(span trace.Span, err error) {\n\tif err != nil {\n\t\tspan.RecordError(err)\n\t\tspan.SetStatus(codes.Error, err.Error())\n\t}\n\tspan.End()\n}\n\n// execContext wraps a write statement in an explicit BEGIN/COMMIT to ensure\n// durability when the Dolt server runs with autocommit disabled (the default\n// when started with --no-auto-commit). Without this, writes remain in an\n// ErrStoreClosed is returned when an operation is attempted on a closed store.\nvar ErrStoreClosed = errors.New(\"store is closed\")\n\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 {","sourceCodeStart":988,"sourceCodeEnd":1024,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/store.go#L988-L1024","documentation":"ErrStoreClosed is returned when an operation is attempted on a Dolt store that has already been closed. It is a lifecycle sentinel checked by operations such as IterIssues, iterIssuesWithDepType, and wakeExpiredDefers before touching the underlying connection. It protects against use-after-close of the store's SQL connection pool.","triggerScenarios":"Calling any store read/write method (e.g. IterIssues) after Store.Close() has been called, or concurrently while Close is in progress — e.g. a background goroutine or deferred task running after shutdown.","commonSituations":"App shutdown ordering issues: background defers/expiry workers still holding the store after Close; double-Close then reuse; long-running query goroutines outliving the CLI command's store lifetime.","solutions":["Ensure all operations complete before calling Store.Close(); close the store last in shutdown.","Guard goroutines/deferred workers with a done channel or context cancelled before Close.","Check for double-close or accidental reuse of a closed store instance; reopen the store if continued use is needed.","If it appears in wakeExpiredDefers, ensure the expiry worker is stopped before closing the store."],"exampleFix":"// before\nstore.Close()\nissues, err := store.IterIssues(ctx) // ErrStoreClosed\n// after\nissues, err := store.IterIssues(ctx)\nif err == nil {\n    store.Close()\n}","handlingStrategy":"type-guard","validationCode":"// guard before use\nselect {\ncase <-storeClosed:\n    return ErrStoreClosed\ndefault:\n}","typeGuard":"func isStoreClosed(err error) bool { return errors.Is(err, dolt.ErrStoreClosed) }","tryCatchPattern":"if errors.Is(err, dolt.ErrStoreClosed) {\n    // reopen the store or terminate the worker gracefully\n}","preventionTips":["Close the store only after all workers/defers are stopped","Use a done channel or context to coordinate shutdown","Never reuse a store instance after Close","Ensure wakeExpiredDefers/expiry workers shut down before Close"],"tags":["dolt","lifecycle","store","closed","concurrency"],"backgroundTag":"use-after-close","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}