benbjohnson/litestream · error

open temp ltx file

Error message

open temp ltx file: %w

What it means

Litestream failed to open the temporary LTX file it uses to stage WAL pages before promoting it to a final LTX file during sync. `db.openLTXFile` calls os.OpenFile with O_RDWR|O_CREATE|O_TRUNC; any open failure (other than disk-full, which is reported as ErrDiskFull) is wrapped here. This means replication for that transaction aborted before any LTX data was written.

Solutions

  1. Check that the directory holding the temp LTX file exists and is writable by the litestream user (ls -ld, touch a test file).
  2. Verify the filesystem is not mounted read-only (mount | grep).
  3. Fix ownership/permissions on the replica/LTX directory (chown -R litestream:litestream <dir>).
  4. Check for a stale entry with the temp filename that is a directory instead of a file.
  5. Inspect disk-full separately — if dmesg/df show ENOSPC this code path would instead surface ErrDiskFull.

Example fix

// before (root-created replica dir, litestream runs as litestream user)
drwx------ root root /var/lib/litestream
// after
chown -R litestream:litestream /var/lib/litestream && chmod 750 /var/lib/litestream
Defensive patterns

Strategy: validation

Validate before calling

const dir = filepath.Dir(ltxStagingPath)
if st, err := os.Stat(dir); err != nil || !st.IsDir() {
    return fmt.Errorf("staging dir missing: %s", dir)
}
probe := filepath.Join(dir, ".litestream-probe")
if err := os.WriteFile(probe, []byte("x"), 0o644); err != nil {
    return fmt.Errorf("staging dir not writable: %w", err)
}
os.Remove(probe)

Try / catch

var pathErr *os.PathError
if errors.As(err, &pathErr) {
    log.Printf("staging file open failed at %s: %v (check perms/existence)", pathErr.Path, pathErr.Err)
}

Prevention

When it happens

Trigger: Calling sync/replication when the LTX staging directory does not exist, the process lacks write permission on it, the temp filename collides with a directory, or the filesystem is read-only.

Common situations: Running litestream as a non-root user against a DB whose replica dir was created by root; mounting the litestream data dir read-only; cleaning up the replica path while litestream runs; SELinux/AppArmor denying writes.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


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

Appendix: source

Thrown at db.go:2125

	if !info.snapshotting && sz == 0 {
		db.Logger.Log(ctx, internal.LevelTrace, "sync: skip", "reason", "no new wal pages")
		return result, nil
	}

	tmpFilename := filename + ".tmp"
	if err := internal.MkdirAll(filepath.Dir(tmpFilename), db.dirInfo); err != nil {
		if isDiskFullError(err) {
			return result, NewLTXError("stage-mkdir", tmpFilename, 0, uint64(txID), uint64(txID), fmt.Errorf("%w: %w", ErrDiskFull, err))
		}
		return result, err
	}

	ltxFile, err := db.openLTXFile(tmpFilename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
	if err != nil {
		if isDiskFullError(err) {
			return result, NewLTXError("stage-open", tmpFilename, 0, uint64(txID), uint64(txID), fmt.Errorf("%w: %w", ErrDiskFull, err))
		}
		return result, fmt.Errorf("open temp ltx file: %w", err)
	}
	defer func() { _ = os.Remove(tmpFilename) }()
	defer func() { _ = ltxFile.Close() }()

	uid, gid := internal.Fileinfo(db.fileInfo)
	_ = os.Chown(tmpFilename, uid, gid)

	db.Logger.Log(ctx, internal.LevelTrace, "encode header",
		"txid", txID.String(),
		"commit", commit,
		"walOffset", info.offset,
		"walSize", sz,
		"salt1", rd.salt1,
		"salt2", rd.salt2)

	timestamp := time.Now()
	enc, err := ltx.NewEncoder(ltxFile)
	if err != nil {

View on GitHub (pinned to 4ed7a308f6)