benbjohnson/litestream · error

rename txid file: %w

Error message

rename txid file: %w

What it means

After writing and fsyncing the temp file, WriteTXIDFile atomically renames '<outputPath>-txid.tmp' to '<outputPath>-txid'. This error means os.Rename failed, so the new TXID was never published; the previous TXID file (if any) remains untouched. Rename failures typically indicate a path-level problem rather than data corruption.

Source

Thrown at replica.go:1744

		return fmt.Errorf("create txid temp file: %w", err)
	}
	defer f.Close()
	defer os.Remove(tmpPath)

	if _, err := fmt.Fprintln(f, txid); err != nil {
		return fmt.Errorf("write txid: %w", err)
	}

	if err := f.Sync(); err != nil {
		return fmt.Errorf("sync txid file: %w", err)
	}

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

	if err := os.Rename(tmpPath, txidPath); err != nil {
		return fmt.Errorf("rename txid file: %w", err)
	}

	if err := internal.FsyncDir(filepath.Dir(txidPath)); err != nil {
		return fmt.Errorf("sync txid dir: %w", err)
	}
	return nil
}

// ReadTXIDFile reads the TXID from a sidecar file at <outputPath>-txid.
// Returns 0, nil if the file does not exist (first run).
func ReadTXIDFile(outputPath string) (ltx.TXID, error) {
	txidPath := TXIDPath(outputPath)

	data, err := os.ReadFile(txidPath)
	if os.IsNotExist(err) {
		return 0, nil
	} else if err != nil {
		return 0, fmt.Errorf("read txid file: %w", err)

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Verify the directory of the output path exists and is writable by the litestream process
  2. Ensure the temp file and target are on the same filesystem (rename cannot cross devices) — keep outputPath on one volume
  3. Check permissions and path length for the '<outputPath>-txid' location
  4. Re-run restore/replication; the temp file is cleaned up by defer so a retry starts clean

Example fix

// before: writing temp file in a different filesystem than target
tmpPath := "/tmp/mydb-txid.tmp" // then rename to /var/lib/db/mydb-txid
// after
tmpPath := finalPath + ".tmp" // same directory, same filesystem
Defensive patterns

Strategy: validation

Validate before calling

dir := filepath.Dir(TXIDPath(outputPath))
if st, err := os.Stat(dir); err != nil || !st.IsDir() {
    return fmt.Errorf("output dir missing: %s", dir)
}

Type guard

func renameable(tmp, dst string) bool {
    return filepath.Dir(tmp) == filepath.Dir(dst) // same fs required
}

Try / catch

if err := WriteTXIDFile(outputPath, txid); err != nil {
    if errors.Is(err, syscall.EXDEV) || errors.Is(err, syscall.ENOENT) {
        // recreate dir / keep temp file next to target, then retry
    }
    return err
}

Prevention

When it happens

Trigger: os.Rename(tmpPath, txidPath) failing because the target directory was removed mid-operation, the output path crosses filesystem boundaries (rename EXDEV), permission denied on the directory, or the target path is invalid/too long.

Common situations: Output directory deleted or recreated while litestream is running; output path pointing into a mounted volume where temp file and target end up on different devices; read-only or permission-restricted restore directories; restore path with a very long name.

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/a77144734eef0925. Report an issue: GitHub.