benbjohnson/litestream · error

cannot restore, SQLite sidecar path already exists: %s. Use

Error message

cannot restore, SQLite sidecar path already exists: %s. Use -force to overwrite

What it means

prepareOutputPath also checks SQLite sidecar files (<output>-wal, <output>-shm, <output>-journal). If any of these exists and -force was not passed, restore aborts, because leaving a stale WAL/SHM/journal next to a freshly restored database can corrupt it or resurrect old data. The error names the specific sidecar path that collided.

Source

Thrown at cmd/litestream/restore.go:285

func (c *RestoreCommand) prepareOutputPath(path string, force bool) error {
	info, err := os.Stat(path)
	if os.IsNotExist(err) {
		return nil
	} else if err != nil {
		return fmt.Errorf("cannot access output path: %w", err)
	}
	if info.IsDir() {
		return fmt.Errorf("cannot restore, output path is a directory: %s", path)
	}

	if info.Size() > 0 && !force {
		return fmt.Errorf("cannot restore, output path already exists and is not empty: %s. Use -force to overwrite", path)
	}

	for _, sidecarPath := range []string{path + "-wal", path + "-shm", path + "-journal"} {
		if _, err := os.Stat(sidecarPath); err == nil && !force {
			return fmt.Errorf("cannot restore, SQLite sidecar path already exists: %s. Use -force to overwrite", sidecarPath)
		} else if err != nil && !os.IsNotExist(err) {
			return fmt.Errorf("cannot access SQLite sidecar path: %w", err)
		}
	}

	for _, removePath := range []string{path, path + "-wal", path + "-shm", path + "-journal"} {
		if err := os.Remove(removePath); err != nil && !os.IsNotExist(err) {
			return fmt.Errorf("remove existing output path: %w", err)
		}
	}
	return nil
}

// loadFromURL creates a replica & updates the restore options from a replica URL.
func (c *RestoreCommand) loadFromURL(ctx context.Context, replicaURL string, ifDBNotExists bool, opt *litestream.RestoreOptions) (*litestream.Replica, error) {
	if opt.OutputPath == "" {
		return nil, &usageError{
			message: "-o is required when restoring from a replica URL",

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Re-run the restore with -force, which removes the stale sidecar files before restoring
  2. Manually delete the reported sidecar file (and the db if desired), then re-run
  3. Ensure no application is running against that database path, then restore

Example fix

// before
rm /data/app.db
litestream restore -o /data/app.db /path/to/db
cannot restore, SQLite sidecar path already exists: /data/app.db-wal. Use -force to overwrite
// after
litestream restore -force -o /data/app.db /path/to/db
Defensive patterns

Strategy: validation

Validate before calling

for _, ext := range []string{"-wal", "-shm", "-journal"} {
    if _, err := os.Stat(outputPath + ext); err == nil && !forceFlag {
        return fmt.Errorf("stale sidecar %s exists; pass -force", outputPath + ext)
    }
}

Try / catch

if err := cmd.Run(ctx); err != nil {
    if strings.Contains(err.Error(), "SQLite sidecar path already exists") {
        // stop the app using that path, then re-run with -force
    }
    return err
}

Prevention

When it happens

Trigger: Restoring to a path where a previous database (or running SQLite connection) left -wal/-shm/-journal files behind, without -force. The check `os.Stat(sidecarPath); err == nil && !force` fires.

Common situations: Restoring over the location of a database that was recently in use (SQLite left the -wal/-shm behind after an unclean shutdown); retrying a restore into the same path after deleting only the .db file; a running app still holding the database at that path.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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