benbjohnson/litestream · error

cannot access output path: %w

Error message

cannot access output path: %w

What it means

prepareOutputPath calls os.Stat on the requested restore output path and wraps any unexpected stat failure with this error. It means the OS could not stat the path for a reason other than non-existence — most commonly a permission problem on a parent directory or an I/O error. Non-existence is explicitly tolerated (restore will create the file), so this signals a real environmental problem.

Source

Thrown at cmd/litestream/restore.go:273

	if opt.TXID != 0 {
		return opt.TXID.String()
	}
	if opt.Follow {
		return ""
	}
	infos, err := litestream.CalcRestorePlan(ctx, r.Client, opt.TXID, opt.Timestamp, r.Logger())
	if err != nil || len(infos) == 0 {
		return ""
	}
	return infos[len(infos)-1].MaxTXID.String()
}

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"} {

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check permissions on every path component of the output path (ls -ld on each parent) and fix with chown/chmod
  2. Verify all intermediate path components are directories, not files
  3. Check `dmesg`/disk health if a filesystem error is suspected
  4. Run the restore as a user with access to the target location

Example fix

// before
litestream restore -o /var/lib/db/app.db replica
cannot access output path: stat /var/lib/db/app.db: permission denied
// after
sudo chown litestream:litestream /var/lib/db
litestream restore -o /var/lib/db/app.db replica
Defensive patterns

Strategy: validation

Validate before calling

import "os"
import "path/filepath"

// Ensure the output location is statable and writable before restoring.
dir := filepath.Dir(outputPath)
if _, err := os.Stat(dir); err != nil {
    return fmt.Errorf("output dir not accessible: %w", err)
}
f, err := os.CreateTemp(dir, ".restore-check")
if err != nil {
    return fmt.Errorf("output dir not writable: %w", err)
}
f.Close()
os.Remove(f.Name())

Try / catch

if err := cmd.Run(ctx); err != nil {
    var pe *fs.PathError
    if errors.As(err, &pe) {
        return fmt.Errorf("check access to %s: %w", pe.Path, pe.Err)
    }
    return err
}

Prevention

When it happens

Trigger: os.Stat(outputPath) returns an error that is not os.IsNotExist — e.g. a parent directory lacks search permission, the path component is not a directory, or an I/O error occurs while accessing the path.

Common situations: Restoring into a directory the current user cannot traverse (e.g. /var/lib owned by root); a typo'd path where a component is a file, not a directory; a dangling mount or full/EIO disk.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


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