gastownhall/beads · error

backup destination is not a directory: %s

Error message

backup destination is not a directory: %s

What it means

Returned by BackupDatabase when the destination path exists but is a regular file (or other non-directory), not a directory. Dolt file:// backup remotes must point at a directory, so the store rejects the argument with this plain (non-wrapped) message.

Source

Thrown at internal/storage/embeddeddolt/version_control.go:737

	})
}

func (s *EmbeddedDoltStore) BackupRemove(ctx context.Context, name string) error {
	return s.withMutatingDBConn(ctx, func(db versioncontrolops.DBConn) error {
		return versioncontrolops.BackupRemove(ctx, db, name)
	})
}

// BackupDatabase registers dir as a file:// Dolt backup remote and syncs
// the database to it. The dir must exist locally. This preserves full Dolt
// commit history.
func (s *EmbeddedDoltStore) BackupDatabase(ctx context.Context, dir string) error {
	info, err := os.Stat(dir)
	if err != nil {
		return fmt.Errorf("backup destination does not exist: %w", err)
	}
	if !info.IsDir() {
		return fmt.Errorf("backup destination is not a directory: %s", dir)
	}

	backupURL, err := versioncontrolops.DirToFileURL(dir)
	if err != nil {
		return err
	}
	backupName := "backup_export"

	return s.withMutatingDBConn(ctx, func(db versioncontrolops.DBConn) error {
		// Register as a backup remote (idempotent — remove first if exists).
		_ = versioncontrolops.BackupRemove(ctx, db, backupName)
		if err := versioncontrolops.BackupAdd(ctx, db, backupName, backupURL); err != nil {
			// Another backup (e.g. "default" registered by `bd backup init`) may
			// already point to this URL. In that case, sync using the existing
			// remote name rather than failing.
			if conflict := versioncontrolops.ExtractAddressConflictName(err); conflict != "" {
				if syncErr := versioncontrolops.BackupSync(ctx, db, conflict); syncErr != nil {
					return fmt.Errorf("sync to backup: %w", syncErr)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Pass a directory path, not a file: mkdir the intended backup dir and re-run
  2. If a file exists at the path, remove/rename it and create a directory
  3. Verify with `ls -la <path>` that the target is a directory
  4. Use DirToFileURL-compatible absolute directory paths

Example fix

// before
store.BackupDatabase(ctx, "/backups/beads.tar.gz") // a file
// after
os.MkdirAll("/backups/beads", 0o755)
store.BackupDatabase(ctx, "/backups/beads")
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(dir)
if err != nil || !info.IsDir() {
    return fmt.Errorf("%%s must be an existing directory", dir)
}

Prevention

When it happens

Trigger: Calling BackupDatabase(ctx, dir) where os.Stat(dir) succeeds but info.IsDir() is false — e.g. passing a tarball path, a config file, or the database file itself instead of a directory.

Common situations: Pointing backup at an archive file expecting it to 'restore into' it; passing the .bdb/db file instead of its parent directory; a symlink resolving to a file.

Related errors


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