gastownhall/beads · error

failed to rename temp file: %w

Error message

failed to rename temp file: %w

What it means

atomicWriteFile finishes by renaming the fully-written temp file over the destination; this wraps os.Rename failure. The temp file is removed, leaving any pre-existing destination intact.

Source

Thrown at cmd/bd/backup_export.go:120

	tmpPath := tmp.Name()

	if _, err := tmp.Write(data); err != nil {
		_ = tmp.Close()
		_ = os.Remove(tmpPath)
		return fmt.Errorf("failed to write temp file: %w", err)
	}
	if err := tmp.Sync(); err != nil {
		_ = tmp.Close()
		_ = os.Remove(tmpPath)
		return fmt.Errorf("failed to sync temp file: %w", err)
	}
	if err := tmp.Close(); err != nil {
		_ = os.Remove(tmpPath)
		return fmt.Errorf("failed to close temp file: %w", err)
	}
	if err := os.Rename(tmpPath, path); err != nil {
		_ = os.Remove(tmpPath)
		return fmt.Errorf("failed to rename temp file: %w", err)
	}
	return nil
}

// runBackupExport performs a Dolt-native backup to .beads/backup/.
// Returns the updated state.
func runBackupExport(ctx context.Context, force bool) (*backupState, error) {
	dir, err := backupDir()
	if err != nil {
		return nil, err
	}

	state, err := loadBackupState(dir)
	if err != nil {
		return nil, err
	}

	// Change detection: skip if nothing changed (unless forced)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check that .beads/backup_state.json is not a directory: if it is, remove it (rm -rf .beads/backup_state.json).
  2. Ensure .beads still exists and is writable at rename time; avoid concurrent processes deleting it.
  3. If .beads is a symlink, per the code's comment resolve it with filepath.EvalSymlinks semantics — keep .beads on the same filesystem as the workspace.
  4. Retry after resolving; the temp file was cleaned up automatically.

Example fix

// shell
file .beads/backup_state.json    # if 'directory':
rm -rf .beads/backup_state.json && bd sync
Defensive patterns

Strategy: validation

Validate before calling

const p = ".beads/backup_state.json"
if st, err := os.Lstat(p); err == nil && st.IsDir() {
    os.RemoveAll(p) // rename over a non-empty dir always fails
}
// ensure dest stays on the same filesystem as its temp file
if st, err := os.Stat(".beads"); err == nil && st.Mode()&os.ModeSymlink != 0 {
    if resolved, err := filepath.EvalSymlinks(".beads"); err == nil {
        _ = resolved // verify same-device with os.Stat + syscall same-device check
    }
}

Type guard

func renameTargetOk(path string) bool {
    st, err := os.Lstat(path)
    return err != nil || !st.IsDir()
}

Try / catch

if err := saveBackupState(dir, state); err != nil {
    if strings.Contains(err.Error(), "rename temp file") {
        _ = os.RemoveAll(filepath.Join(dir, "backup_state.json"))
        if retryErr := saveBackupState(dir, state); retryErr != nil {
            return retryErr
        }
    } else if err != nil {
        return err
    }
}

Prevention

When it happens

Trigger: os.Rename(tmpPath, path) fails: destination directory became unwritable/removed between create and rename, destination path changed type (e.g. backup_state.json is now a non-empty directory), or cross-filesystem rename (path relocated via symlink between mounts).

Common situations: Another process deleted/recreated .beads mid-run; .beads/backup_state.json exists as a directory; exotic filesystems lacking atomic rename; the symlink-target caveat noted in the file's comments (path resolving across filesystems via symlink).

Related errors


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