benbjohnson/litestream · error

sync ltx dir: %w

Error message

sync ltx dir: %w

What it means

After renaming the LTX file, litestream fsyncs the parent directory so the rename is durable across crashes. A failure here is wrapped as 'sync ltx dir:'; like the rename failure, it clears cached file-info and position state because durability of the new LTX file is not guaranteed.

Source

Thrown at db.go:2244

	// 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(),
	}

	encPos := enc.PostApplyPos()
	result.pos = &encPos

	// Track the logical end of WAL content for checkpoint decisions.
	// This is the WALOffset + WALSize from the LTX we just created.
	// Using this instead of file size prevents issue #997 where stale
	// frames with old salt values cause perpetual checkpoint triggering.

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Confirm the LTX directory exists and is openable by the process at failure time.
  2. If the filesystem does not support directory fsync, relocate the data directory to a POSIX-compliant local filesystem.
  3. Retry the sync; if the rename actually persisted, the next successful sync repairs cached state.
  4. Investigate cleanup jobs that might delete the LTX directory during active syncs.

Example fix

// before
path: /mnt/efs/db   # NFS: dir fsync unsupported/erratic
// after
path: /var/lib/litestream/db  # local ext4/xfs
Defensive patterns

Strategy: try-catch

Type guard

func supportsDirFsync(dir string) error {
	return internal.FsyncDir(dir) // pre-flight in setup
}

Try / catch

if err := db.Sync(ctx); err != nil {
	if strings.Contains(err.Error(), "sync ltx dir") {
		// verify dir fsync support; consider relocating storage
	}
	return err
}

Prevention

When it happens

Trigger: internal.FsyncDir(filepath.Dir(filename)) at db.go:2239 returned an error: the directory fd could not be opened (permissions, deleted dir), or fsync on directories is unsupported by the filesystem (some NFS/FUSE mounts, certain Windows configurations).

Common situations: Data directory on a network or FUSE filesystem that rejects directory fsync; the LTX directory removed concurrently by retention cleanup; permission changes preventing opening the directory.

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/be0ae35ec04abc73. Report an issue: GitHub.