benbjohnson/litestream · error

open temp ltx file: %w

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.

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)

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.