From 6f2423c52d27a9d8f13ca170dd474ccd5c9023f1 Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Mon, 6 Jul 2026 00:28:15 -0600 Subject: [PATCH] refactor: dedupe busy/locked error detection into isBusyOrLockedError Extracts the /\b(busy|locked|SQLITE_BUSY|SQLITE_LOCKED)\b/i regex check into a single isBusyOrLockedError(msg) helper, replacing the identical inline check duplicated between openRepo's native path (tryOpenRepoNative) and openReadonlyWithNative's native path. No change to matching behavior or the busy_timeout value. DEFAULTS.db.busyTimeoutMs (this issue's other flagged cleanup) was already added and wired into openDb/openReadonlyOrFail by an earlier commit in this stack (8f23020d); this commit covers the remaining regex duplication only. docs check acknowledged: internal dedup-only refactor, no CLI surface, language support, or documented architecture/design decision changed. Fixes #1749 Impact: 3 functions changed, 27 affected --- src/db/connection.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/db/connection.ts b/src/db/connection.ts index 92cc6901..a4ec10d3 100644 --- a/src/db/connection.ts +++ b/src/db/connection.ts @@ -438,6 +438,16 @@ function openRepoNative(customDbPath?: string): { repo: Repository; close(): voi } } +/** + * True when an error message indicates the SQLite database is busy or locked + * (SQLITE_BUSY/SQLITE_LOCKED). Shared by openRepo()'s and + * openReadonlyWithNative()'s native-path catch blocks so the two call sites + * can't drift. + */ +function isBusyOrLockedError(msg: string): boolean { + return /\b(busy|locked|SQLITE_BUSY|SQLITE_LOCKED)\b/i.test(msg); +} + /** Validate and wrap an injected `opts.repo` Repository instance (no DB opened). */ function wrapInjectedRepo(repo: Repository): { repo: Repository; close(): void } { if (!(repo instanceof Repository)) { @@ -468,7 +478,7 @@ function tryOpenRepoNative( // Re-throw locking/busy errors — falling back to better-sqlite3 would // hit the same contention (and potentially hang without busy_timeout). const msg = toErrorMessage(e); - if (/\b(busy|locked|SQLITE_BUSY|SQLITE_LOCKED)\b/i.test(msg)) { + if (isBusyOrLockedError(msg)) { throw new DbError(`Database is busy (another process may be writing): ${msg}`, {}); } debug(`openRepo: native path failed, falling back to better-sqlite3: ${msg}`); @@ -553,7 +563,7 @@ export function openReadonlyWithNative( nativeDb = native.NativeDatabase.openReadonly(dbPath); } catch (e) { const msg = toErrorMessage(e); - if (/\b(busy|locked|SQLITE_BUSY|SQLITE_LOCKED)\b/i.test(msg)) { + if (isBusyOrLockedError(msg)) { debug(`openReadonlyWithNative: native path busy, skipping native DB: ${msg}`); } else { debug(`openReadonlyWithNative: native path failed: ${msg}`);