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 = &ltx.FileInfo{
		Level:     0,
		MinTXID:   txID,
		MaxTXID:   txID,
		CreatedAt: time.Now(),
		Size:      enc.N(),
	}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Ensure the LTX destination directory exists and is writable by the litestream user.
  2. Verify temp file and final LTX path are on the same filesystem/mount to avoid EXDEV.
  3. Check for cleanup/compaction processes deleting the target directory concurrently.
  4. 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

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.