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

  1. Re-open the store with a writable open path (OpenForReadOnlyCommand / OpenForWorkingSetReconcile exist precisely because normal opens must be writable)
  2. Check errors.Is(err, embeddeddolt.ErrReadOnly) and skip/handle the incidental write instead of failing the command
  3. 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

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


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