laurent22/joplin · critical · Error

Cannot open database: ${error.message ?? error}: ${JSON.stri

Error message

Cannot open database: ${error.message ?? error}: ${JSON.stringify(options)}

What it means

Thrown by Database.open() when the underlying driver's open(options) rejects. The driver is platform-specific (better-sqlite3 on desktop, a web SQL driver, or an RN driver), and the options object — typically { name: <db file path> } — is JSON-serialised into the message. Common root causes are a locked/corrupt database file, missing file system permissions, or an invalid/missing database name.

Source

Thrown at packages/lib/database.ts:64

	public setLogger(l: Logger) {
		this.logger_ = l;
	}

	public logger() {
		return this.logger_;
	}

	public driver() {
		return this.driver_;
	}

	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Open options vary per driver: better-sqlite3 expects { name }, web/RN drivers accept additional fields
	public async open(options: any) {
		try {
			await this.driver().open(options);
		} catch (error) {
			throw new Error(`Cannot open database: ${error.message ?? error}: ${JSON.stringify(options)}`);
		}

		this.logger().info('Database was open successfully');
	}

	public async close() {
		try {
			await this.driver().close?.();
		} catch (error) {
			this.logger().warn('Failed to close database', error);
		}
	}

	public escapeField(field: string) {
		if (field === '*') return '*';
		const p = field.split('.');
		if (p.length === 1) return `\`${field}\``;
		if (p.length === 2) return `${p[0]}.\`${p[1]}\``;

View on GitHub (pinned to 2654b33620)

Solutions

  1. Ensure no other Joplin process has the same profile/database open.
  2. Verify write permissions and existence of the profile directory and the database file path.
  3. If corrupt, restore from backup or delete the database file so Joplin recreates it (data loss for that profile).
  4. After a Node/Electron upgrade, rebuild native modules (yarn rebuild better-sqlite3) to fix ABI mismatches.
  5. On Windows, exclude the profile folder from antivirus real-time scanning.

Example fix

// before
await db.open({ name: dbPath }); // throws if locked/corrupt

// after — surface the driver error and guard against concurrent opens
try {
  await db.open({ name: dbPath });
} catch (error) {
  logger.error('Database open failed', error);
  // recover: restore backup, recreate DB, or prompt user
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before opening, confirm the path is writable and not locked by another process
import shim from './shim';
const exists = await shim.fsDriver().exists(dbPath);
const parentDir = shim.fsDriver().dirname(dbPath);
const parentExists = await shim.fsDriver().exists(parentDir);
if (!parentExists) {
  // create the profile directory or surface the misconfiguration
  return;
}

Type guard

function isDatabaseOpenError(e: unknown): e is Error {
  return e instanceof Error && /^Cannot open database/.test(e.message);
}

Try / catch

try {
  await database.open({ name: dbPath });
} catch (error) {
  if (isDatabaseOpenError(error)) {
    // inspect the wrapped driver message; recover (backup/restore) or fail fast
    logger.error('DB open failed', error);
    throw error;
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling database.open({ name: '/path/to/database.sqlite' }) when the driver cannot open the file: another process holds a lock on the SQLite file, the path is not writable, the file is corrupt (SQLITE_CORRUPT), the profile directory doesn't exist, or better-sqlite3 was not built for the current Node ABI.

Common situations: Two Joplin instances opening the same profile; the profile directory was deleted or is on a read-only mount; a crash left a stale -wal/-shm lock; a Node/Electron upgrade broke the native better-sqlite3 binding (ABI mismatch); antivirus locking the DB file on Windows.

Related errors


AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12). Data as JSON: /api/errors/ec29a18672d82982. Report an issue: GitHub.