benbjohnson/litestream · error

open db: %w

Error message

open db: %w

What it means

Store.RegisterDB opens the database (db.Open) outside the store lock; any failure is wrapped as "open db: %w". The cause is whatever db.Open hit — typically the SQLite file is missing, unreadable, corrupt, or the WAL/SHM cannot be created. Because this is a disaster-recovery tool the open failure is returned to the caller rather than swallowed.

Source

Thrown at store.go:317

			s.mu.Unlock()
			return nil
		}
	}
	s.mu.Unlock()

	// Apply store-wide settings before opening the database.
	db.SetLogger(s.Logger.With(LogKeyDB, filepath.Base(db.Path())))
	db.L0Retention = s.L0Retention
	db.ShutdownSyncTimeout = s.ShutdownSyncTimeout
	db.ShutdownSyncInterval = s.ShutdownSyncInterval
	db.VerifyCompaction = s.VerifyCompaction
	db.RetentionEnabled = s.RetentionEnabled
	db.Done = s.done

	// Open the database without holding the lock to avoid blocking other operations.
	// The double-check pattern below handles the race condition.
	if err := db.Open(); err != nil {
		return fmt.Errorf("open db: %w", err)
	}

	// Second check: verify database wasn't added by another goroutine while we were opening.
	// If it was, close our instance and return without error.
	s.mu.Lock()

	for _, existing := range s.dbs {
		if existing.Path() == db.Path() {
			// Another goroutine added this database while we were opening.
			// Release lock before closing to avoid potential deadlock.
			s.mu.Unlock()
			if err := db.Close(context.Background()); err != nil {
				db.Logger.Error("close duplicate db", "path", db.Path(), "error", err)
			}
			return nil
		}
	}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Unwrap the error to see the underlying cause (os.PathError / SQLite error code)
  2. Verify the database file exists at the configured path and the process has read/write access to the db, -wal, and -shm files
  3. Ensure the application has created the database before registering it (or create it with sqlite3 first)
  4. Check disk space and volume mount status

Example fix

// before
litestream.yml: dbpath: /data/app.db  # file not created yet

// after
$ sqlite3 /data/app.db "PRAGMA journal_mode=WAL; SELECT 1;"
$ # then start litestream
Defensive patterns

Strategy: fallback

Validate before calling

info, err := os.Stat(dbPath)
if err != nil { return err } // missing file
if info.IsDir() { return fmt.Errorf("%s is a directory", dbPath) }
if f, err := os.OpenFile(dbPath, os.O_RDWR, 0); err != nil { return err } else { f.Close() }

Try / catch

if err := store.RegisterDB(db); err != nil {
    if strings.HasPrefix(err.Error(), "open db:") {
        // unwrap cause: missing file, permissions, or corruption; fix environment, don't blind-retry
        log.Printf("db open failed: %v", err)
    }
}

Prevention

When it happens

Trigger: RegisterDB is called (directly or via handleRegister / handlePotentialDatabase) and db.Open fails: file does not exist, permission denied on db or its -wal/-shm files, SQLite file corruption, directory missing, or disk I/O error.

Common situations: Pointing litestream at a path where the application has not created the SQLite file yet; running litestream as a different user than the app; container volume not mounted; WAL file left unwritable after permission changes.

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/cddcc33343633a47. Report an issue: GitHub.