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

  1. Check the wrapped driver error for lock vs permission vs schema cause.
  2. Ensure the DB file (or remote endpoint) is writable and no other process holds the lock.
  3. Run migrations/doctor so the metadata table exists.
  4. 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

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


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