benbjohnson/litestream · error

open database: %w

Error message

open database: %w

What it means

populateDatabase calls os.Remove on any existing database, then opens the (new) file with `sql.Open("sqlite3", c.DB)` and wraps failures as "open database". Since database/sql's Open is lazy, this error generally means a bad DSN/path or an unavailable driver rather than a runtime connection problem; actual file-level failures typically appear at the first Exec (see "set page size").

Source

Thrown at cmd/litestream-test/populate.go:76

		"page_size", c.PageSize,
	)

	if err := c.populateDatabase(ctx, targetBytes); err != nil {
		return fmt.Errorf("populate database: %w", err)
	}

	slog.Info("Database population complete", "db", c.DB)
	return nil
}

func (c *PopulateCommand) populateDatabase(ctx context.Context, targetBytes int64) error {
	if err := os.Remove(c.DB); err != nil && !os.IsNotExist(err) {
		slog.Warn("Could not remove existing database", "error", err)
	}

	db, err := sql.Open("sqlite3", c.DB)
	if err != nil {
		return fmt.Errorf("open database: %w", err)
	}
	defer db.Close()

	if _, err := db.Exec(fmt.Sprintf("PRAGMA page_size = %d", c.PageSize)); err != nil {
		return fmt.Errorf("set page size: %w", err)
	}

	if _, err := db.Exec("PRAGMA journal_mode = WAL"); err != nil {
		return fmt.Errorf("set journal mode: %w", err)
	}

	if _, err := db.Exec("PRAGMA synchronous = NORMAL"); err != nil {
		return fmt.Errorf("set synchronous: %w", err)
	}

	for i := 0; i < c.TableCount; i++ {
		tableName := fmt.Sprintf("test_table_%d", i)

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Ensure the -db value is a file path in a writable directory (not a directory itself) and check parent-directory write permissions.
  2. Verify the sqlite3 driver is registered in the build (correct import/CGO setup) and rebuild if needed.
  3. If the real failure appears only on the first Exec, look at the subsequent wrapped errors ("set page size", "set journal mode") for the file-level root cause.

Example fix

// before
db, err := sql.Open("sqlite3", c.DB)
if err != nil {
    return fmt.Errorf("open database: %w", err)
}
// after — detect file-level open failures eagerly
db, err := sql.Open("sqlite3", c.DB)
if err != nil {
    return fmt.Errorf("open database: %w", err)
}
if err := db.Ping(); err != nil {
    return fmt.Errorf("open database: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

import { statSync } from "fs";
if (existsSync(dbPath) && statSync(dbPath).isDirectory()) {
  throw new Error(`-db must be a file path, not a directory: ${dbPath}`);
}
if (!statSync(path.dirname(dbPath)).isDirectory()) throw new Error("parent directory missing: " + path.dirname(dbPath));

Type guard

function isWritableFilePath(p) {
  try { return !existsSync(p) || statSync(p).isFile(); } catch { return false; }
}

Try / catch

try {
  await run("litestream-test", ["populate", "-db", dbPath]);
} catch (e) {
  if (String(e).includes("open database")) {
    console.error(`Could not open ${dbPath}; check it is a file path in a writable directory and the sqlite3 driver is built in.`);
  }
  throw e;
}

Prevention

When it happens

Trigger: `litestream-test populate -db <path>` where the DSN is rejected by the driver, the sqlite3 driver is not registered in the build, or the driver fails immediately opening the path (unwritable directory, path is a directory, invalid characters in path).

Common situations: -db pointing at an existing directory instead of a file path; unwritable parent directory; building without the sqlite3 driver import; path characters that break the DSN.

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