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

  1. Ensure `dir` in the config is the real (non-symlink) absolute path of the directory.
  2. Use filepath.EvalSymlinks on the directory before configuring so dirPath and discovered dbPath share a base.
  3. 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

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


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