gastownhall/beads · error

rotating %s -> %s: %w

Error message

rotating %s -> %s: %w

What it means

rotateLogIfOversized wraps os.Rename(primary, rotated) failures as "rotating %s -> %s: %w". Rename overwrites the destination on both Unix and Windows, so this error means the log could not be moved aside when it exceeded maxBytes — typically a cross-device move, permission problem, or the destination path being occupied in a way Rename cannot replace (e.g. a directory).

Source

Thrown at internal/doltserver/logrotate.go:98

func rotateLogIfOversized(primary string, maxBytes int64) (bool, error) {
	if maxBytes <= 0 {
		return false, nil
	}
	info, err := os.Stat(primary)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, fmt.Errorf("stat %s: %w", primary, err)
	}
	if info.Size() <= maxBytes {
		return false, nil
	}
	rotated := rotatedLogPath(primary)
	// os.Rename overwrites the destination on both Unix and Windows (Go's
	// Rename wraps MoveFileEx with MOVEFILE_REPLACE_EXISTING on Windows).
	if err := os.Rename(primary, rotated); err != nil {
		return false, fmt.Errorf("rotating %s -> %s: %w", primary, rotated, err)
	}
	return true, nil
}

// maybeRotateLog is the convenience wrapper used by Start(). It rotates the
// dolt-server log if it is oversized and emits a debug message on both
// rotation and error paths. It never returns an error — rotation is
// best-effort and must not block server startup.
func maybeRotateLog(beadsDir string) {
	primary := logPath(beadsDir)
	max := maxLogBytes()
	rotated, err := rotateLogIfOversized(primary, max)
	if err != nil {
		debug.Logf("doltserver: log rotation failed for %s: %v", primary, err)
		return
	}
	if rotated {
		debug.Logf("doltserver: rotated %s -> %s (exceeded %d bytes)", primary, rotatedLogPath(primary), max)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check that the rotated path (e.g. server.log.1) is not a directory; remove or rename it.
  2. Ensure the bd user has write permission on the directory containing the log (needed for rename).
  3. Move the log directory onto a single filesystem, or delete/move the oversized log manually and restart.
  4. Copy-then-delete manually if a cross-device rename is unavoidable: cp server.log server.log.1 && rm server.log.

Example fix

// before (broken state)
$ ls .beads/logs
server.log  server.log.1/   # rotated name is a directory
// after
$ rm -rf .beads/logs/server.log.1
$ bd start   # rotation now succeeds
Defensive patterns

Strategy: validation

Validate before calling

rotated := rotatedLogPath(primary)
if info, err := os.Lstat(rotated); err == nil && info.IsDir() {
    return fmt.Errorf("%s is a directory; cannot overwrite via rename", rotated)
}
// source and destination must share a device
s1, _ := os.Stat(filepath.Dir(primary))
s2, _ := os.Stat(filepath.Dir(rotated))
if s1 != nil && s2 != nil && !sameDevice(s1, s2) {
    return fmt.Errorf("cross-device rename would fail")
}

Try / catch

rotated, err := rotateLogIfOversized(primary, maxBytes)
if err != nil {
    var pe *fs.PathError
    if errors.As(err, &pe) && errors.Is(pe.Err, syscall.EXDEV) {
        // fall back to copy+delete
    }
    return err
}

Prevention

When it happens

Trigger: The primary log exceeds maxBytes and os.Rename to rotatedLogPath(primary) fails: rotated path exists as a directory, source and destination are on different filesystems, or permissions prevent the rename in either directory.

Common situations: An old rotated log file was replaced by a directory of the same name; the log lives in a container volume while the rotated name resolves elsewhere; running bd without write permission on the log directory after a user change.

Related errors


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