benbjohnson/litestream · error

create txid temp file: %w

Error message

create txid temp file: %w

What it means

WriteTXIDFile wraps os.Create failure when creating the temporary file (<txid-path>.tmp) used to atomically persist the restored database's last TXID next to the output. The wrapped cause carries the OS-level reason (permissions, missing directory, disk full).

Source

Thrown at replica.go:1726

	}
	return next.CreatedAt.Before(curr.CreatedAt)
}

// TXIDPath returns the path to the TXID sidecar file for the given database path.
// Uses -txid suffix to match SQLite's naming convention for associated files (-wal, -shm).
func TXIDPath(outputPath string) string {
	return outputPath + "-txid"
}

// 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)

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Ensure the restore output directory exists and is writable by the litestream process user.
  2. Check available disk space (df -h) and inode/quota limits.
  3. Restore to a different path, then move the files into place.
  4. In containers, mount the target volume with write permissions.

Example fix

// before
$ litestream restore -o /readonly/db.sqlite db
// error: create txid temp file: open /readonly/db.sqlite.txid.tmp: permission denied
// after
$ mkdir -p /data && chown litestream /data
$ litestream restore -o /data/db.sqlite db
Defensive patterns

Strategy: validation

Validate before calling

// Go: verify the output directory is writable before restore
dir := filepath.Dir(outputPath)
if info, err := os.Stat(dir); err != nil || !info.IsDir() {
    return fmt.Errorf("output dir missing: %s", dir)
}
probe, err := os.CreateTemp(dir, ".litestream-probe-*")
if err != nil {
    return fmt.Errorf("output dir not writable: %w", err)
}
probe.Close(); os.Remove(probe.Name())

Try / catch

if err := WriteTXIDFile(path, txid); err != nil {
    var perr *fs.PathError
    if errors.As(err, &perr) {
        // perr.Err: EACCES/ENOENT/ENOSPC -> fix dir permissions or free space
    }
    return err
}

Prevention

When it happens

Trigger: Restore completing to an output path whose parent directory does not exist, is read-only, or where creating `<output>.txid.tmp` fails (EACCES, ENOSPC, read-only filesystem, sandboxed container FS).

Common situations: Restore target inside a non-writable container path; SELinux/AppArmor restrictions; output directory created only after restore started; full disk during DR recovery.

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