benbjohnson/litestream · error

cannot access SQLite sidecar path: %w

Error message

cannot access SQLite sidecar path: %w

What it means

prepareOutputPath in the litestream restore command verifies that the restore target path and its SQLite sidecar files (-wal, -shm, -journal) can be safely written. For each sidecar path it calls os.Stat; if stat fails with an error other than NotExist (e.g. permission denied on the parent directory, or the path component is not a directory), it wraps and returns that error. This is a fail-fast guard so restore never silently proceeds into a target it cannot inspect.

Source

Thrown at cmd/litestream/restore.go:287

	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",
			hint:    fmt.Sprintf("litestream restore -o /path/to/db %s", replicaURL),
		}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check permissions on the output directory: run `ls -la` on the target path's parent and ensure the litestream process user can read/execute it
  2. Verify every component of the target path is a directory: `stat` each component of the path
  3. If the path is on a network mount, verify the mount is healthy (`df`, remount if stale)
  4. Restore to a different writable path and move the file into place afterwards

Example fix

// before
litestream restore -o /var/lib/app/db.sqlite mydb
// error: cannot access SQLite sidecar path: stat /var/lib/app/db.sqlite-wal: permission denied

// after — fix directory ownership first
sudo chown litestream:litestream /var/lib/app
litestream restore -o /var/lib/app/db.sqlite mydb
Defensive patterns

Strategy: validation

Validate before calling

for _, suffix := range []string{"-wal", "-shm", "-journal"} {
	if _, err := os.Stat(outputPath + suffix); err != nil && !os.IsNotExist(err) {
		// abort: cannot inspect target
	}
}
if fi, err := os.Stat(filepath.Dir(outputPath)); err != nil || !fi.IsDir() {
	// abort: output directory missing or not a directory
}

Prevention

When it happens

Trigger: Running `litestream restore` when os.Stat on <db>-wal, <db>-shm, or <db>-journal returns a non-NotExist error: the parent directory lacks read/execute permission, a path component is a file not a directory, or an I/O error occurs while resolving the path (e.g. stale NFS mount, broken symlink loop).

Common situations: Restoring into a directory owned by another user or with restrictive permissions (common in Docker containers running as non-root); restoring to a path whose parent was replaced by a regular file; network filesystems returning transient EIO/ESTALE on stat.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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