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
- Check that the directory holding the temp LTX file exists and is writable by the litestream user (ls -ld, touch a test file).
- Verify the filesystem is not mounted read-only (mount | grep).
- Fix ownership/permissions on the replica/LTX directory (chown -R litestream:litestream <dir>).
- Check for a stale entry with the temp filename that is a directory instead of a file.
- 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
- Provision and chown the litestream data directory to the litestream user before startup
- Never mount the litestream state directory read-only
- Exclude litestream staging dirs from external cleanup/rotation jobs
- Verify writable staging dir in your deployment readiness probe
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.