benbjohnson/litestream · error

rename L0 file: %w

Error message

rename L0 file: %w

What it means

The final step stages the fetched LTX file by atomically renaming tmpPath to the final LTX path within the L0 directory. This error wraps os.Rename failing, meaning the fetched file could not be adopted even though it downloaded successfully.

Source

Thrown at db.go:1657

	defer func() { _ = os.Remove(tmpPath) }() // Clean up temp file on error

	if _, err := io.Copy(tmpFile, reader); err != nil {
		_ = tmpFile.Close()
		return fmt.Errorf("copy L0 file: %w", err)
	}

	if err := tmpFile.Sync(); err != nil {
		_ = tmpFile.Close()
		return fmt.Errorf("sync L0 file: %w", err)
	}

	if err := tmpFile.Close(); err != nil {
		return fmt.Errorf("close L0 file: %w", err)
	}

	// Atomically rename temp file to final path
	if err := os.Rename(tmpPath, localPath); err != nil {
		return fmt.Errorf("rename L0 file: %w", err)
	}
	db.invalidatePosCache()

	db.Logger.Info("fetched latest L0 file from replica",
		"min_txid", minTXID,
		"max_txid", maxTXID)

	return nil
}

// verify ensures the current LTX state matches where it left off from
// the real WAL. Check info.ok if verification was successful.
func (db *DB) verify(ctx context.Context, state *syncState) (info syncInfo, err error) {
	pos, err := db.Pos()
	if err != nil {
		return info, fmt.Errorf("pos: %w", err)
	}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Ensure nothing else (cron jobs, antivirus, other litestream instances) touches the LTX directory while litestream runs.
  2. Check that localPath is not an existing directory (ls -ld) and remove bad state, or run `litestream reset` to rebuild local LTX state.
  3. Verify tmp and final paths are on the same filesystem (they should be — both derive from LTXPath in the same dir); fix bind-mount changes.
  4. Inspect the wrapped OS error for the exact path and errno, then let the next sync retry.

Example fix

// before: cron cleaner removes *.tmp mid-fetch
*/5 * * * * find /var/lib/db -name '*.tmp' -delete
// after: exclude the litestream LTX dir from cleanup
*/5 * * * * find /var/lib/db -name '*.tmp' -not -path '*/ltx/*' -delete
Defensive patterns

Strategy: validation

Validate before calling

// Ensure no competing cleaner or stale target before fetch
if fi, err := os.Lstat(localPath); err == nil && fi.IsDir() {
    log.Fatalf("target path is a directory: %s", localPath)
}

Type guard

null

Try / catch

if err := os.Rename(tmpPath, localPath); err != nil {
    return fmt.Errorf("rename L0 file: %w", err)
} // if persistent, `litestream reset` rebuilds local LTX state cleanly

Prevention

When it happens

Trigger: checkDatabaseBehindReplica calls os.Rename(tmpPath, localPath) after a successful Close and the rename fails — the target path exists in a way rename can't replace (directory), the L0 dir permissions changed, the source .tmp was removed by an external cleaner, or the filesystem doesn't support atomic rename across the involved paths.

Common situations: A cleanup job (tmpwatch/cron) deleted the .tmp between close and rename; target path turned into a directory due to a prior corrupt state; different filesystems for temp and target paths after a bind-mount change; Windows/limited network filesystems with weak rename semantics.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/6bfd05a182f2778f. Report an issue: GitHub.