benbjohnson/litestream · error
cannot restore, output path is a directory: %s
Error message
cannot restore, output path is a directory: %s
What it means
prepareOutputPath stats the output path and rejects it if it resolves to a directory. Restore must write a SQLite database file at exactly that path, and os.WriteFile/overwrite semantics cannot replace a directory, so the command fails fast instead of producing a confusing write error later.
Source
Thrown at cmd/litestream/restore.go:276
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"} {
if err := os.Remove(removePath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("remove existing output path: %w", err)
}View on GitHub (pinned to 4ed7a308f6)
Solutions
- Pass a file path (e.g. /data/app.db) instead of a directory as the -o output path
- Remove or rename the existing directory at that path, then re-run the restore
- Restore into a new path inside the directory, e.g. -o /some/dir/app.db
Example fix
// before mkdir -p restored litestream restore -o restored /path/to/db # fails: output path is a directory // after litestream restore -o restored/app.db /path/to/db
Defensive patterns
Strategy: validation
Validate before calling
info, err := os.Stat(outputPath)
if err == nil && info.IsDir() {
return fmt.Errorf("output path %s is a directory; pass a file path", outputPath)
} Try / catch
if err := cmd.Run(ctx); err != nil {
if strings.Contains(err.Error(), "output path is a directory") {
// fix the -o argument to point at a file path
}
return err
} Prevention
- -o must be the target database FILE path, not a directory
- Do not pre-create the output path as a directory (e.g. via mkdir -p)
- If you want the file inside a directory, restore to dir/name.db
When it happens
Trigger: Running `litestream restore -o /some/dir` where /some/dir already exists as a directory (os.Stat succeeds and info.IsDir() is true).
Common situations: A wrapper pre-creates the output directory with `mkdir -p` before restoring (a habit from other tools); a previous restore or tool created a directory at the target; passing a directory instead of a file path by mistake.
Related errors
- failed to calculate relative path for %s: %w
- cannot access output path: %w
- cannot restore, output path already exists and is not empty:
- cannot access SQLite sidecar path: %w
- remove existing output path: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/08d982c04bb7231b.
Report an issue: GitHub.