benbjohnson/litestream · critical

meta-path collision: databases %s and %s would share meta-pa

Error message

meta-path collision: databases %s and %s would share meta-path %s, causing replication state corruption

What it means

Litestream computes a metadata path for every database discovered via directory replication. If two databases would resolve to the same meta-path, both would read/write the same replication state and corrupt each other's checkpoints. The function detects this during setup and aborts, naming both colliding databases and the shared path.

Source

Thrown at cmd/litestream/main.go:855

	if len(dbPaths) == 0 && !dbc.Watch {
		return nil, fmt.Errorf("no SQLite databases found in directory %s with pattern %s", dirPath, dbc.Pattern)
	}

	// Create DB instances for each found database
	var dbs []*litestream.DB
	metaPaths := make(map[string]string)

	for _, dbPath := range dbPaths {
		db, err := newDBFromDirectoryEntry(dbc, dirPath, dbPath)
		if err != nil {
			return nil, fmt.Errorf("failed to create DB for %s: %w", dbPath, err)
		}

		// Validate unique meta-path to prevent replication state corruption
		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)
	}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Use meta-dir instead of meta-path — it derives a unique per-database path from the relative path automatically.
  2. Remove duplicate/symlinked files so each discovered database has a distinct relative path.
  3. If using a custom meta-path derivation, ensure it incorporates the per-database relative path.
  4. Restore only one of the colliding databases from backup instead of both.

Example fix

# before
- dir: /data
  pattern: "*.db"
  meta-path: /var/lib/litestream/meta   # same for every db -> collision

# after
- dir: /data
  pattern: "*.db"
  meta-dir: /var/lib/litestream/meta    # per-db: <meta-dir>/<rel-path>.litestream
Defensive patterns

Strategy: validation

Validate before calling

seen := map[string]string{}
for _, p := range dbPaths {
    rel, _ := filepath.Rel(dirPath, p)
    mp := filepath.Join(metaDir, rel+".litestream")
    if prev, ok := seen[mp]; ok {
        return fmt.Errorf("%s and %s collide on meta-path %s", prev, p, mp)
    }
    seen[mp] = p
}

Prevention

When it happens

Trigger: In the per-DB loop, db.MetaPath() returns a value already present in the metaPaths map — e.g. two distinct database files whose relative paths collapse to the same meta path (symlinks to the same file, case-insensitive filesystem collisions, or a custom meta-path template that doesn't vary per relative path).

Common situations: Using meta-path with a template that omits the per-DB component; symlinked duplicates in the scanned directory; a filesystem where two filenames differ only in case but the meta store is case-insensitive.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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