benbjohnson/litestream · error
failed to calculate relative path for %s: %w
Error message
failed to calculate relative path for %s: %w
What it means
newDBFromDirectoryEntry computes each database's path relative to the replication directory root (used to namespace meta paths and replica paths). filepath.Rel fails when the two paths cannot be related — typically when dbPath is not actually under dirPath (e.g. via symlink resolution) or a path is malformed. The failure is wrapped with the database path.
Source
Thrown at cmd/litestream/main.go:871
if mp := db.MetaPath(); mp != "" {
if existingDB, exists := metaPaths[mp]; exists {
return nil, fmt.Errorf("meta-path collision: databases %s and %s would share meta-path %s, causing replication state corruption", existingDB, dbPath, mp)
}
metaPaths[mp] = dbPath
}
dbs = append(dbs, db)
}
return dbs, nil
}
// newDBFromDirectoryEntry creates a DB instance for a database discovered via directory replication.
func newDBFromDirectoryEntry(dbc *DBConfig, dirPath, dbPath string) (*litestream.DB, error) {
// Calculate relative path from directory root
relPath, err := filepath.Rel(dirPath, dbPath)
if err != nil {
return nil, fmt.Errorf("failed to calculate relative path for %s: %w", dbPath, err)
}
// Create a copy of the config for the discovered database
dbConfigCopy := *dbc
dbConfigCopy.Path = dbPath
dbConfigCopy.Dir = "" // Clear dir field for individual DB
dbConfigCopy.Pattern = "" // Clear pattern field
dbConfigCopy.Recursive = false // Clear recursive flag
dbConfigCopy.Watch = false // Individual DBs do not watch directories
// Ensure every database discovered beneath a directory receives a unique
// metadata path. Without this, all databases share the same meta-path and
// clobber each other's replication state.
switch {
case dbc.MetaDir != nil:
baseMetaDir, err := expand(*dbc.MetaDir)
if err != nil {
return nil, fmt.Errorf("failed to expand meta dir for %s: %w", dbPath, err)View on GitHub (pinned to 4ed7a308f6)
Solutions
- Ensure `dir` in the config is the real (non-symlink) absolute path of the directory.
- Use filepath.EvalSymlinks on the directory before configuring so dirPath and discovered dbPath share a base.
- Check the wrapped filepath.Rel error message for the exact two paths involved and align them.
Example fix
// before
dirPath, _ := expand(dbc.Dir) // /data is a symlink to /mnt/data
// after
dirPath, _ := expand(dbc.Dir)
if resolved, err := filepath.EvalSymlinks(dirPath); err == nil {
dirPath = resolved
} Defensive patterns
Strategy: validation
Validate before calling
if resolved, err := filepath.EvalSymlinks(dirPath); err == nil {
dirPath = resolved
}
if _, err := filepath.Rel(dirPath, dbPath); err != nil {
return fmt.Errorf("db %s is not under dir %s", dbPath, dirPath)
} Prevention
- Configure litestream with the real, symlink-resolved, absolute directory path.
- Avoid scanning directories that mix absolute and relative path bases.
- Test filepath.Rel on your layout before deploying.
When it happens
Trigger: filepath.Rel(dirPath, dbPath) returns an error: dbPath is not a subpath of dirPath (rooted vs relative mismatch, symlinked directory resolved differently), or one of the paths is empty/malformed.
Common situations: Scanned directory is itself a symlink so FindSQLiteDatabases returns paths that don't lexically sit under dirPath; mixing absolute dir with relative db paths; platform path-separator mismatch.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- failed to scan directory %s: %w
- cannot restore, output path is a directory: %s
- get database position: %w
- remove L0 directory: %w
- recreate L0 directory: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/3416f9ff861eaa13.
Report an issue: GitHub.