gastownhall/beads · error

write metadata.json: %w

Error message

write metadata.json: %w

What it means

In `finalizeSyncedBootstrap`, after a synced bootstrap the config (`metadata.json`) is updated so the database field points at the Dolt database name (defaulting to `"dolt"` rather than the legacy placeholder). If `cfg.Save(beadsDir)` fails to write metadata.json, the error is wrapped as `write metadata.json: ...` and bootstrap finalization aborts.

Source

Thrown at cmd/bd/bootstrap.go:835

	// required by configfile.Load consumers.
	cfg.Backend = configfile.BackendDolt
	cfg.DoltDatabase = dbName
	switch {
	case cfg.IsDoltProxiedServerMode():
		cfg.DoltMode = configfile.DoltModeProxiedServer
	case cfg.IsDoltServerMode() || doltserver.IsSharedServerMode():
		cfg.DoltMode = configfile.DoltModeServer
	default:
		cfg.DoltMode = configfile.DoltModeEmbedded
	}
	// Mirror init's convention: metadata.json database points at the Dolt
	// directory rather than the legacy "beads.db" placeholder.
	if cfg.Database == "" || cfg.Database == beads.CanonicalDatabaseName {
		cfg.Database = "dolt"
	}

	if err := cfg.Save(beadsDir); err != nil {
		return fmt.Errorf("write metadata.json: %w", err)
	}

	if err := createConfigYaml(beadsDir, false, ""); err != nil {
		return fmt.Errorf("create config.yaml: %w", err)
	}
	if err := doctor.EnsureGitignoreForBeadsDir(beadsDir); err != nil {
		return fmt.Errorf("ensure .beads/.gitignore: %w", err)
	}

	// Persist sync.remote so subsequent fresh clones (and bd bootstrap
	// retries) can rediscover the remote without re-probing origin refs.
	if syncRemote != "" {
		if err := config.SetYamlConfigInDir(beadsDir, "sync.remote", syncRemote); err != nil {
			return fmt.Errorf("persist sync.remote to config.yaml: %w", err)
		}
	}

	return nil

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check writability of `.beads/metadata.json`: fix ownership/permissions (`chown -R $USER .beads`).
  2. If metadata.json is a directory or corrupt, remove it and re-run `bd bootstrap` (it will rewrite config files).
  3. Free disk space if the wrapped error reports I/O or no-space failures.
  4. Re-run `bd bootstrap` — finalization is designed to be idempotent (see TestFinalizeSyncedBootstrapIsIdempotent).

Example fix

// before (shell)
$ sudo bd bootstrap   # .beads now owned by root
$ bd bootstrap        // write metadata.json: open ...: permission denied
// after
$ sudo chown -R $USER .beads
$ bd bootstrap        # succeeds
Defensive patterns

Strategy: validation

Validate before calling

# Pre-flight before finalize (bootstrap) runs:
BEADS_DIR=.beads
[ -d "$BEADS_DIR" ] || { echo "missing $BEADS_DIR"; exit 1; }
if [ -e "$BEADS_DIR/metadata.json" ] && [ -d "$BEADS_DIR/metadata.json" ]; then echo "metadata.json is a directory"; exit 1; fi
[ -w "$BEADS_DIR" ] || { echo "$BEADS_DIR not writable by $(whoami)"; exit 1; }

Try / catch

if err := finalizeSyncedBootstrap(ctx, beadsDir, plan, cfg); err != nil {
    if strings.Contains(err.Error(), "write metadata.json:") {
        // finalization is idempotent — fix permissions and re-run bootstrap
        return fmt.Errorf("repair .beads permissions, then re-run bd bootstrap: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: `bd bootstrap` finishing a sync action calls `finalizeSyncedBootstrap`; saving the updated config fails — `.beads` directory not writable, disk full, metadata.json exists as a directory, or permission conflicts.

Common situations: Read-only or root-owned `.beads` after cloning with sudo; a stray `metadata.json/` directory; interrupted earlier bootstrap leaving bad permissions; CI running as a different user than the clone owner.

Related errors


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