benbjohnson/litestream · error
rename ltx file: %w
Error message
rename ltx file: %w
What it means
The completed temporary LTX file could not be atomically renamed to its final path. Litestream wraps the rename error with 'rename ltx file:' and, because the file may or may not exist in its final location, it clears the max-LTX-file-info cache and invalidates the position cache to avoid stale assumptions.
Source
Thrown at db.go:2237
}
if err := ltxFile.Close(); err != nil {
if isDiskFullError(err) {
return result, NewLTXError("stage-close", tmpFilename, 0, uint64(txID), uint64(txID), fmt.Errorf("%w: %w", ErrDiskFull, err))
}
return result, fmt.Errorf("close ltx file: %w", err)
}
// Atomically rename file to final path.
db.setSyncDiagPhase(diagPhaseRenameLTX, func(s *diagState) {
s.txID = txID
s.walSize = sz
})
if err := os.Rename(tmpFilename, filename); err != nil {
db.maxLTXFileInfos.Lock()
delete(db.maxLTXFileInfos.m, 0) // clear cache if in unknown state
db.maxLTXFileInfos.Unlock()
db.invalidatePosCache()
return result, fmt.Errorf("rename ltx file: %w", err)
}
if err := internal.FsyncDir(filepath.Dir(filename)); err != nil {
db.maxLTXFileInfos.Lock()
delete(db.maxLTXFileInfos.m, 0) // clear cache if in unknown state
db.maxLTXFileInfos.Unlock()
db.invalidatePosCache()
return result, fmt.Errorf("sync ltx dir: %w", err)
}
result.synced = true
result.l0FileInfo = <x.FileInfo{
Level: 0,
MinTXID: txID,
MaxTXID: txID,
CreatedAt: time.Now(),
Size: enc.N(),
}
View on GitHub (pinned to 4ed7a308f6)
Solutions
- Ensure the LTX destination directory exists and is writable by the litestream user.
- Verify temp file and final LTX path are on the same filesystem/mount to avoid EXDEV.
- Check for cleanup/compaction processes deleting the target directory concurrently.
- After fixing, run `litestream reset` if the position cache is suspected stale, then retry.
Example fix
// before // config with mismatched paths path: /mnt/other-fs/db # temp on different mount // after path: /var/lib/db # same filesystem as temp dir # or ensure both under the same mount point
Defensive patterns
Strategy: try-catch
Validate before calling
const st = fs.statSync(ltxDir)
if (!st.isDirectory() || (st.mode & 0o200) === 0) throw new Error('LTX dir missing or not writable') Type guard
func sameFilesystem(a, b string) bool {
da, db := devOf(a), devOf(b)
return da == db // avoid EXDEV on rename
} Try / catch
if err := db.Sync(ctx); err != nil {
if strings.Contains(err.Error(), "rename ltx file") {
// check dir existence/permissions and mount layout
}
return err
} Prevention
- Keep temp and final LTX paths on the same filesystem.
- Ensure the data directory exists and is writable before startup.
- Don't run retention/cleanup jobs that race with active syncs.
- Use `litestream reset` after suspected stale position state.
When it happens
Trigger: os.Rename(tmpFilename, filename) at db.go:2232 failed: destination directory missing, permission denied, cross-device link (EXDEV) if temp and final paths are on different mounts, or the target is locked by another process.
Common situations: LTX path and temp path on different filesystems after repartitioning or mount changes; the LTX directory deleted by cleanup racing with a sync; permission changes on the data directory; read-only remounts.
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/ed5e2a4cdba50dd7.
Report an issue: GitHub.