benbjohnson/litestream · error

sync txid file

Error message

sync txid file: %w

What it means

WriteTXIDFile persists the replica's last-applied TXID to a sidecar '<db>-txid' file using a temp-file + fsync + rename protocol. This error wraps f.Sync(), meaning the OS failed to flush the temp file's data to stable storage. Litestream fsyncs before the atomic rename so the TXID survives a crash; a sync failure means durability cannot be guaranteed for the written TXID.

Solutions

  1. Free disk space on the volume containing the output path and retry the operation
  2. Check dmesg / system logs for underlying device I/O errors and repair or replace the failing disk
  3. Verify the output path is on a local filesystem that supports fsync, not an unreliable network mount
  4. Delete any stale '<outputPath>-txid.tmp' file and rerun; the code removes the temp file on error via defer

Example fix

// before: silently ignoring the write error
if err := WriteTXIDFile(db.Path(), txid); err != nil { log.Println(err) }
// after: fail loudly so the caller can resync
if err := WriteTXIDFile(db.Path(), txid); err != nil {
    return fmt.Errorf("persist txid for %s: %w", db.Path(), err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: check writable space before writing
if st, err := os.Statfs(filepath.Dir(TXIDPath(outputPath))); err == nil && st.Bavail*uint64(st.Bsize) < 1<<20 {
    return fmt.Errorf("insufficient disk space for txid file")
}

Type guard

func canWriteDir(dir string) bool {
    f, err := os.CreateTemp(dir, ".probe")
    if err != nil { return false }
    f.Close(); os.Remove(f.Name())
    return true
}

Try / catch

if err := WriteTXIDFile(outputPath, txid); err != nil {
    if errors.Is(err, syscall.ENOSPC) {
        // free space / alert, then retry
    } else {
        return fmt.Errorf("persist txid: %w", err)
    }
}

Prevention

When it happens

Trigger: Calling WriteTXIDFile (directly or via replica restore/apply flows) when the filesystem rejects fsync: full disk on the volume holding the output path, I/O errors on the underlying device, or filesystems/containers where fsync is not supported on the temp file '<outputPath>-txid.tmp'.

Common situations: Disk-full on the host or in a Docker container after a large restore; NFS or network filesystems with unreliable fsync; failing disk reported through writeback errors; running in environments (some overlayfs setups) where fsync on a freshly created file errors.

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

Appendix: source

Thrown at replica.go:1736

// WriteTXIDFile atomically writes a TXID to a sidecar file at <outputPath>-txid.
// Uses temp-file + fsync + rename for crash safety.
func WriteTXIDFile(outputPath string, txid ltx.TXID) error {
	txidPath := TXIDPath(outputPath)
	tmpPath := txidPath + ".tmp"

	f, err := os.Create(tmpPath)
	if err != nil {
		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).

View on GitHub (pinned to 4ed7a308f6)