gastownhall/beads · error
ErrReadOnly
ErrReadOnly
Error message
embeddeddolt: store is read-only
What it means
ErrReadOnly is the sentinel for write attempts on an embedded Dolt store opened in read-only mode. Any mutating helper (withMutatingDBConn, withMutatingPinnedDBConn, ApplySchemaMigrations, etc.) returns it instead of performing the write. It is deliberately distinct from lifecycle failures so the CLI can suppress it for incidental post-command writes like tip metadata.
Source
Thrown at internal/storage/embeddeddolt/readonly.go:17
package embeddeddolt
import "errors"
// ErrReadOnly is returned when a write is attempted on a store opened
// read-only — OpenReadOnly, or OpenForPreviewCommand for a --dry-run/--inspect
// command.
//
// It is exported (and declared here, without a cgo build tag) because callers
// outside this package have to be able to tell "this store refuses writes by
// construction" apart from a real failure. The CLI's post-command
// tip-metadata write is the case that forced it: that write is incidental
// bookkeeping which read-only opens have always tolerated because
// OpenForReadOnlyCommand is deliberately writable, and a store that genuinely
// refuses it must not turn an otherwise successful command into a non-zero
// exit after the fact.
var ErrReadOnly = errors.New("embeddeddolt: store is read-only")
View on GitHub (pinned to 71377f2769)
Solutions
- Re-open the store with a writable open path (OpenForReadOnlyCommand / OpenForWorkingSetReconcile exist precisely because normal opens must be writable)
- Check errors.Is(err, embeddeddolt.ErrReadOnly) and skip/handle the incidental write instead of failing the command
- Close other processes holding the database lock that forced read-only mode, then reopen
Example fix
// before
if err := store.ApplySchemaMigrations(ctx); err != nil { return err }
// after
if err := store.ApplySchemaMigrations(ctx); err != nil {
if errors.Is(err, embeddeddolt.ErrReadOnly) {
return nil // read-only open: migrations not applicable
}
return err
} Defensive patterns
Strategy: type-guard
Validate before calling
if store.ReadOnly() { // or check open mode before write calls
skipIncidentalWrites = true
} Type guard
func isReadOnlyErr(err error) bool { return errors.Is(err, embeddeddolt.ErrReadOnly) } Try / catch
if err := doWrite(ctx, store); err != nil {
if errors.Is(err, embeddeddolt.ErrReadOnly) {
return nil // tolerate incidental write on read-only store
}
return err
} Prevention
- Track the open mode (read-only vs writable) and route writes accordingly
- Use errors.Is against the ErrReadOnly sentinel instead of string matching
- Skip post-command bookkeeping writes when the store was opened read-only
When it happens
Trigger: Calling any write path (ApplySchemaMigrations, withMutatingDBConn/withMutatingPinnedDBConn closures, working-set/tip writes) on a store opened via a read-only open (e.g. preview/readonly open paths).
Common situations: Running a command against a repo opened read-only (locked database, preview mode, another process holds the DB) and the command or a post-command hook attempts a mutation.
Related errors
- ErrTransaction
- ErrQuery
- ErrScan
- invalid database name: %q; hyphens are not allowed in embedd
- embeddeddolt: requires CGO (build with CGO_ENABLED=1)
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/3e80ad8bb571fa58.
Report an issue: GitHub.