gastownhall/beads · error
db: SetMetadata %s: %w
Error message
db: SetMetadata %s: %w
What it means
SetMetadata writes a key/value pair into the metadata table using REPLACE INTO. This error wraps any failure of that Exec: database read-only or locked, metadata table missing, permission denied, connection loss, or context cancellation. Since metadata persists cross-machine state (sync keys, prefixes), a failure here usually blocks higher-level operations.
Source
Thrown at internal/storage/domain/db/config.go:42
}
var _ domain.ConfigSQLRepository = (*configSQLRepositoryImpl)(nil)
func (r *configSQLRepositoryImpl) GetMetadata(ctx context.Context, key string) (string, error) {
var value string
err := r.runner.QueryRowContext(ctx, "SELECT value FROM metadata WHERE `key` = ?", key).Scan(&value)
if errors.Is(err, sql.ErrNoRows) {
return "", nil
}
if err != nil {
return "", fmt.Errorf("db: GetMetadata %s: %w", key, err)
}
return value, nil
}
func (r *configSQLRepositoryImpl) SetMetadata(ctx context.Context, key, value string) error {
if _, err := r.runner.ExecContext(ctx, "REPLACE INTO metadata (`key`, value) VALUES (?, ?)", key, value); err != nil {
return fmt.Errorf("db: SetMetadata %s: %w", key, err)
}
return nil
}
func (r *configSQLRepositoryImpl) GetLocalMetadata(ctx context.Context, key string) (string, error) {
var value string
err := r.runner.QueryRowContext(ctx, "SELECT value FROM local_metadata WHERE `key` = ?", key).Scan(&value)
if errors.Is(err, sql.ErrNoRows) {
return "", nil
}
if err != nil {
return "", fmt.Errorf("db: GetLocalMetadata %s: %w", key, err)
}
return value, nil
}
func (r *configSQLRepositoryImpl) SetLocalMetadata(ctx context.Context, key, value string) error {
if _, err := r.runner.ExecContext(ctx, "REPLACE INTO local_metadata (`key`, value) VALUES (?, ?)", key, value); err != nil {View on GitHub (pinned to 71377f2769)
Solutions
- Check the wrapped driver error for lock vs permission vs schema cause.
- Ensure the DB file (or remote endpoint) is writable and no other process holds the lock.
- Run migrations/doctor so the metadata table exists.
- Free disk space / restore network connectivity, then retry the write.
Example fix
// before
// read-only replica
configRepo.SetMetadata(ctx, "issue_prefix", "bd") // fails
// after
if !dbWritable(db) {
return fmt.Errorf("database is read-only; cannot set metadata")
}
configRepo.SetMetadata(ctx, "issue_prefix", "bd") Defensive patterns
Strategy: retry
Validate before calling
// verify writability before metadata writes
if err := runner.PingContext(ctx); err != nil {
return fmt.Errorf("database unavailable: %w", err)
}
// also ensure no competing writer holds the DB lock Type guard
null
Try / catch
if err := configRepo.SetMetadata(ctx, key, value); err != nil {
if isLockedOrTransient(errors.Unwrap(err)) {
return retryWithBackoff(ctx, func() error { return configRepo.SetMetadata(ctx, key, value) })
}
return fmt.Errorf("cannot persist metadata %s: %w", key, err)
} Prevention
- Run only one bd writer per database at a time (or rely on file locking semantics).
- Never point bd at a read-only mount/replica for mutating commands.
- Keep disk space and connectivity healthy before sync operations.
- Migrate legacy databases so the metadata table exists.
When it happens
Trigger: ExecContext("REPLACE INTO metadata ...") returns an error — SQLite/Dolt file locked by a concurrent writer, database opened read-only or on a read-only filesystem, schema missing the metadata table, or network failure to a remote Dolt server.
Common situations: Two bd processes writing simultaneously; running bd from a read-only mount or read-only replica; writing to a legacy database without the metadata table; disk full.
Related errors
- db: GetMetadata %s: %w
- failed to begin transaction: %w
- failed to recompute is_blocked: %w
- failed to commit is_blocked repairs: %w
- failed to query orphaned dependencies: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e197f02f7d65bca3.
Report an issue: GitHub.