benbjohnson/litestream · error

remove ltx directory: %w

Error message

remove ltx directory: %w

What it means

Raised during local state reset (litestream reset / DB reset path) when os.RemoveAll on the database's LTX directory fails with an error other than not-exist. The wrapper is 'remove ltx directory: %w'. Reset aborts so a half-deleted local state is never silently reused.

Source

Thrown at db.go:532

	db.metaPath = path
}

// LTXDir returns path of the root LTX directory.
func (db *DB) LTXDir() string {
	return filepath.Join(db.metaPath, "ltx")
}

// ResetLocalState removes local LTX files, forcing a fresh snapshot on next sync.
// This is useful for recovering from corrupted or missing LTX files.
// The database file itself is not modified.
func (db *DB) ResetLocalState(ctx context.Context) error {
	db.Logger.Info("resetting local litestream state",
		"meta_path", db.metaPath,
		"ltx_dir", db.LTXDir())

	// Remove all LTX files
	if err := os.RemoveAll(db.LTXDir()); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("remove ltx directory: %w", err)
	}

	// Clear cached LTX file info
	db.maxLTXFileInfos.Lock()
	db.maxLTXFileInfos.m = make(map[int]*ltx.FileInfo)
	db.maxLTXFileInfos.Unlock()

	db.invalidatePosCache()

	db.Logger.Info("local state reset complete, next sync will create fresh snapshot")
	return nil
}

// LTXLevelDir returns path of the given LTX compaction level.
// Panics if level is negative.
func (db *DB) LTXLevelDir(level int) string {
	return filepath.Join(db.LTXDir(), strconv.Itoa(level))
}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Stop the litestream process before resetting so no files are held open
  2. Fix permissions/ownership on the LTX directory (chown/chmod) or run reset as the same user as litestream
  3. Ensure the volume is writable (not read-only mount)
  4. Remove the directory manually as root, then retry
  5. Confirm the path from 'meta_path'/'ltx_dir' in the preceding log line points where you expect

Example fix

// before: reset while daemon holds handles
$ litestream reset /var/lib/db
// remove ltx directory: unlinkat /var/lib/db/ltx: device or resource busy
// after: stop first, then reset
$ systemctl stop litestream
$ litestream reset /var/lib/db
$ systemctl start litestream
Defensive patterns

Strategy: validation

Validate before calling

// Check writability and that no process holds the dir before reset
test := filepath.Join(ltxDir, ".probe")
if err := os.WriteFile(test, nil, 0o600); err != nil {
    return fmt.Errorf("ltx dir not writable: %w", err)
}
os.Remove(test)
// also: pgrep litestream must return nothing

Try / catch

if err := resetDB(dbPath); err != nil {
    if errors.Is(err, os.ErrPermission) {
        // advise running as the litestream user
    }
    return err
}

Prevention

When it happens

Trigger: Calling the DB reset path (db.go reset) while db.LTXDir() cannot be removed — filesystem permission denied, directory busy (open file handles by a running litestream process), read-only mount, or I/O error.

Common situations: Running reset as a user without ownership of /var/lib/litestream; litestream daemon still running and holding files open; container with read-only data volume; NFS/ephemeral storage I/O failures.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/2bf15653e93c4480. Report an issue: GitHub.