Repository navigation
fix(mcp): fall back to node:sqlite when better-sqlite3 binding is missing - #3887
diegosouzapw merged 14 commits into
Conversation
…nc (r5) main fast-forwarded to release/v3.8.25 (diegosouzapw#3863): unblocked Build+Docker via diegosouzapw#3864, plus diegosouzapw#3837 (mimocode proxy) and diegosouzapw#3862 (trivy bump). This marker re-opens the umbrella PR for further v3.8.25 work. No version bump.
…s + combos editor + menu + WS default-on (diegosouzapw#3860) Integrated into release/v3.8.25 — feat(compression-ui): unified compression configuration UI (Compression Hub + per-engine Lite/Aggressive/Ultra pages + combos editor + sidebar entry + live-WS default-on). File-size re-baselined for sidebarVisibility.ts/chatCore.ts growth; orphan ws test relocated to a collected path.
…ributors Audited every commit since v3.8.24 and filled the gaps the [3.8.25] section was missing: a New Features section (compression engines + Compression Studios diegosouzapw#3848, compression UI diegosouzapw#3860, injection-guard diegosouzapw#3857, kiro discovery diegosouzapw#3836, Veo diegosouzapw#3839, mimocode proxy diegosouzapw#3837, Arena ELO flag diegosouzapw#3821), 9 more Fixed entries (diegosouzapw#3811/diegosouzapw#3807/diegosouzapw#3759/diegosouzapw#3849/diegosouzapw#3838/diegosouzapw#3835/diegosouzapw#3814/diegosouzapw#3820/diegosouzapw#3819), a Security section (CCR IDOR diegosouzapw#3859, supply-chain diegosouzapw#3824), and an Internal/Quality section. Every contributor and issue reporter is now credited.
Re-adds CHANGELOG.md (a prior server-side commit accidentally dropped it) with the complete, audited [3.8.25] section: New Features, the full Fixed list, Security & Hardening, and Internal/Quality — every contributor and issue reporter credited.
…, document OMNIROUTE_MAX_PENDING_MIGRATIONS, green the unit suite Release-gate reconciliation for v3.8.25: - CHANGELOG: dated 2026-06-14, linked diegosouzapw#3826, rolled up file-size re-baselines (diegosouzapw#3823/diegosouzapw#3833), recorded the test-greening; re-synced all 41 i18n CHANGELOG mirrors. - Documented OMNIROUTE_MAX_PENDING_MIGRATIONS (diegosouzapw#3416) in .env.example + ENVIRONMENT.md. - Greened the unit suite (was merged red on 4 CI shards): aligned 10 stale tests to this cycle's intended behavior (diegosouzapw#3838/diegosouzapw#3822/diegosouzapw#3501/SOCKS5/Vertex-Express/Antigravity) and the same-provider 503 fall-through test; de-flaked the compression benchmark reproducibility and ServiceSupervisor crash tests. No production code changed.
…rkflow token permissions The Security tab held 155 open alerts, ALL from the advisory OpenSSF Scorecard tool (diegosouzapw#3824) — supply-chain/posture scores, not code vulnerabilities — which drowned out real CodeQL findings. - scorecard.yml: stop uploading SARIF to the code-scanning tab (drop the upload-sarif step + the now-unused security-events: write). The run still produces the OpenSSF badge (publish_results) and a downloadable SARIF artifact. - TokenPermissions hardening (the high-severity, genuinely-valuable subset): set each workflow's top-level token to read-only and grant the exact writes at the job level that needs them — npm-publish (id-token/packages on publish jobs), docker-publish (packages on build), electron-release (contents on build/release, id-token/packages on publish-npm), build-fork (packages on build), claude (empty top-level; job grants its own). The 155 existing alerts were dismissed. Not adopting repo-wide SHA-pinning (143 PinnedDependencies advisories) — declined.
…s cycle's behavior These were red on the CI Integration job (pre-existing). No production code changed: - integration-wiring: the combos page no longer renders a per-page EmailPrivacyToggle (diegosouzapw#3822 consolidated it into Settings → Appearance); the provider-detail test-result masking and upstream-proxy copy moved to decomposed components (diegosouzapw#3501 BatchTestResultsModal / UpstreamProxyCard) — assertions now read the owning files. - api-routes-critical: SOCKS5 is now enabled by default (opt-out), so the disabled- rejection test must set ENABLE_SOCKS5_PROXY=false explicitly (an unset env now means enabled). (The ~32 live-Gemini integration tests are gated on OMNIROUTE_API_KEY and skip in CI; they only 'fail' locally when that key is present without a running server.)
…sing The MCP audit logger was the only place in open-sse that imported better-sqlite3 without a fallback. When the bundled native binary failed to land in dist/node_modules/better-sqlite3/build/Release/ (which happens for some global npm installs and Docker images), the audit logger silently disabled itself and spammed the log with: [MCP Audit] Failed to connect to database: Could not locate the bindings file. Tried: .../better_sqlite3.node The main app already supports a node:sqlite fallback via src/lib/db/adapters/driverFactory.ts. Mirror that pattern here so audit logging keeps working — node:sqlite ships in Node 22.5+ which already covers all supported runtimes. Only treat the native import as failed when the error message matches known binding-ABI failures; corrupt DB / permission errors still surface to the operator.
There was a problem hiding this comment.
Code Review
This pull request introduces a transparent fallback to Node's built-in node:sqlite module (Node 22.5+) for the MCP server's audit logger when the native better-sqlite3 binding is missing or fails to load. While the fallback logic and accompanying unit tests are well-structured, two key issues were identified: the isBindingIssue check is too narrow and should be expanded to cover other common ABI mismatch errors, and the DatabaseSync class lacks an open property, meaning the open state must be tracked locally within the adapter to avoid violating the AuditDatabase interface contract.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const isBindingIssue = | ||
| nativeMessage.includes("Could not locate the bindings file") || | ||
| nativeMessage.includes("NODE_MODULE_VERSION") || | ||
| nativeMessage.includes("ERR_DLOPEN_FAILED"); |
There was a problem hiding this comment.
The error messages for native binding mismatches can also include phrases like "was compiled against a different Node.js version" or "Module did not self-register" (as defined in scripts/dev/ensure-native-sqlite.mjs). If these occur, the fallback to node:sqlite will not be triggered.
We should expand the isBindingIssue check to cover all common ABI mismatch and binding failure messages to ensure the fallback is robust.
const isBindingIssue =
nativeMessage.includes("Could not locate the bindings file") ||
nativeMessage.includes("NODE_MODULE_VERSION") ||
nativeMessage.includes("was compiled against a different Node.js version") ||
nativeMessage.includes("Module did not self-register") ||
nativeMessage.includes("ERR_DLOPEN_FAILED");| interface NodeSqliteDatabase { | ||
| prepare: (sql: string) => { | ||
| run: (...params: unknown[]) => { changes: number | bigint; lastInsertRowid: number | bigint }; | ||
| get: (...params: unknown[]) => unknown; | ||
| all: (...params: unknown[]) => unknown[]; | ||
| }; | ||
| exec: (sql: string) => void; | ||
| close: () => void; | ||
| open: boolean; | ||
| } | ||
|
|
||
| function createNodeSqliteAuditAdapter(db: NodeSqliteDatabase): AuditDatabase { | ||
| return { | ||
| driver: "node:sqlite", | ||
| open: db.open, | ||
| prepare<TRow = unknown>(sql: string) { | ||
| const stmt = db.prepare(sql); | ||
| return { | ||
| get: (...params: unknown[]) => stmt.get(...params) as TRow | undefined, | ||
| all: (...params: unknown[]) => stmt.all(...params) as TRow[], | ||
| run: (...params: unknown[]) => stmt.run(...params), | ||
| }; | ||
| }, | ||
| pragma(pragmaSql: string) { | ||
| // node:sqlite has no .pragma() helper — route through .exec() for | ||
| // statement-shaped PRAGMAs (e.g. "wal_checkpoint(TRUNCATE)"). | ||
| try { | ||
| db.exec(`PRAGMA ${pragmaSql}`); | ||
| return null; | ||
| } catch (err) { | ||
| return err instanceof Error ? err.message : String(err); | ||
| } | ||
| }, | ||
| close: () => db.close(), | ||
| }; | ||
| } |
There was a problem hiding this comment.
The native DatabaseSync class from node:sqlite does not have an open property. Declaring open: boolean on NodeSqliteDatabase and assigning open: db.open results in undefined at runtime, which violates the AuditDatabase interface contract and fails to track the open/closed state of the database adapter.
Instead, track the open state locally within the adapter, similar to how it is done in src/lib/db/adapters/nodeSqliteAdapter.ts.
interface NodeSqliteDatabase {
prepare: (sql: string) => {
run: (...params: unknown[]) => { changes: number | bigint; lastInsertRowid: number | bigint };
get: (...params: unknown[]) => unknown;
all: (...params: unknown[]) => unknown[];
};
exec: (sql: string) => void;
close: () => void;
}
function createNodeSqliteAuditAdapter(db: NodeSqliteDatabase): AuditDatabase {
let isOpen = true;
return {
driver: "node:sqlite",
get open() {
return isOpen;
},
prepare<TRow = unknown>(sql: string) {
const stmt = db.prepare(sql);
return {
get: (...params: unknown[]) => stmt.get(...params) as TRow | undefined,
all: (...params: unknown[]) => stmt.all(...params) as TRow[],
run: (...params: unknown[]) => stmt.run(...params),
};
},
pragma(pragmaSql: string) {
// node:sqlite has no .pragma() helper — route through .exec() for
// statement-shaped PRAGMAs (e.g. "wal_checkpoint(TRUNCATE)").
try {
db.exec("PRAGMA " + pragmaSql);
return null;
} catch (err) {
return err instanceof Error ? err.message : String(err);
}
},
close: () => {
try {
db.close();
} finally {
isOpen = false;
}
},
};
}… open state Round 2 on diegosouzapw#3887, addressing @gemini-code-assist's two review points: 1. Replace the inline 3-pattern `isBindingIssue` with the canonical `isNativeSqliteLoadError` helper from `src/lib/db/core.ts`. The main app already covers every binding/ABI failure mode there (MODULE_NOT_FOUND, ERR_DLOPEN_FAILED, 'Module did not self-register', 'Cannot find module better-sqlite3', 'was compiled against a different Node.js version', and 'Could not locate the bindings file'). Reusing it keeps the two paths from drifting. 2. `node:sqlite` DatabaseSync does not expose a boolean `open` — `open`/`close` are prototype methods, and the only state surface is the `isOpen` getter. Track open state in a closure inside the adapter and expose `open` as a getter. `close()` is now idempotent (no-op if already closed) so a stray double close on the same handle can't throw. The cache is still cleared by the existing `closeAuditDb()` so a re-open always builds a fresh adapter. Test mock no longer carries the bogus `open: true` field, and an extra assertion verifies that `closeAuditDb()` returns false the second time (cache cleared, handle not re-closed).
|
Thanks @gemini-code-assist — both points were correct, fixed in fc6a28c: 1.
Reusing it keeps the MCP audit path from drifting out of sync with the main app's driver factory. Real errors (corrupt db, permission denied) still bubble up to the operator unchanged. 2. Test updated: removed the now-stale Both files syntax-checked with |
There was a problem hiding this comment.
Code Review
This pull request introduces a transparent fallback to Node's built-in node:sqlite module when the native better-sqlite3 bindings are missing or fail to load, ensuring the MCP audit logger remains functional in environments like Docker or global installations. It includes an adapter to align node:sqlite with the existing AuditDatabase interface and adds corresponding unit tests. The feedback suggests letting errors propagate directly from the adapted pragma method to avoid silently swallowing execution failures, and casting the dynamic import path of node:sqlite to never to prevent static analysis or TypeScript compilation issues in older environments.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| pragma(pragmaSql: string) { | ||
| // node:sqlite has no .pragma() helper — route through .exec() for | ||
| // statement-shaped PRAGMAs (e.g. "wal_checkpoint(TRUNCATE)"). | ||
| try { | ||
| db.exec(`PRAGMA ${pragmaSql}`); | ||
| return null; | ||
| } catch (err) { | ||
| return err instanceof Error ? err.message : String(err); | ||
| } | ||
| }, |
There was a problem hiding this comment.
The pragma method in createNodeSqliteAuditAdapter catches errors internally and returns them as strings instead of letting them propagate. This deviates from the expected behavior of pragma (and the behavior of better-sqlite3), which throws on execution failure. Because of this, the try/catch block in closeAuditDb will not catch any WAL checkpoint failures, and the warning log will be silently skipped. We should let the error propagate directly.
pragma(pragmaSql: string) {
// node:sqlite has no .pragma() helper — route through .exec() for
// statement-shaped PRAGMAs (e.g. "wal_checkpoint(TRUNCATE)").
db.exec(`PRAGMA ${pragmaSql}`);
return null;
},| return null; | ||
| } | ||
| try { | ||
| const { DatabaseSync } = (await import("node:sqlite")) as { |
There was a problem hiding this comment.
Using a static import("node:sqlite") can cause TypeScript compilation errors if the build environment uses an older version of @types/node that does not yet declare node:sqlite. It can also cause issues with some bundlers that attempt to statically resolve the module. To prevent this and maintain consistency with how node:sqlite is imported elsewhere in the codebase (e.g., in nodeSqliteAdapter.ts), we should cast the import path as never or string to bypass static analysis.
| const { DatabaseSync } = (await import("node:sqlite")) as { | |
| const { DatabaseSync } = (await import("node:sqlite" as never)) as { |
…dit-sqlite-fallback # Conflicts: # .github/workflows/npm-publish.yml # config/quality/file-size-baseline.json
… test The node:sqlite fallback warning referenced `nativeMessage`, but it was declared with `const` inside the `if (!isNativeSqliteLoadError)` bail-out block (which returns) — so it was out of scope in the fallback path (TS2304 / runtime ReferenceError). Fast Quality Gates does not run typecheck:core, so it slipped past green CI. Hoisted the declaration to the top of the catch block. Also repaired the fallback regression test: a `vi.doMock` factory that itself throws is reported by vitest as a mock-setup error and never reaches the code under test, so the test could not actually exercise the node:sqlite fallback. Switched to a throwing `default` constructor (matches reality: `new Database()` throws when the prebuilt .node is absent). The test now genuinely guards the scope fix — it fails without it. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
|
Thanks, @megamen32! 🙏 A Two adjustments while integrating (co-authored, on your branch):
Also rebased onto |
…sing (diegosouzapw#3887) Integrated into release/v3.8.26
Summary
The MCP audit logger in
open-sse/mcp-server/audit.tswas the only place in the SSE sidecar that importedbetter-sqlite3without a fallback. When the bundled native binary failed to land indist/node_modules/better-sqlite3/build/Release/(which happens for some global npm installs and minimal Docker images where the postinstall binary-copy step does not run for whatever reason), the audit logger silently disabled itself and spammed the logs with:The main app already supports a
node:sqlitefallback viasrc/lib/db/adapters/driverFactory.ts:152-187. This PR mirrors that pattern for the MCP audit module so the same scenario no longer kills audit logging.Repro
dist/node_modules/(e.g.npm i -g omnirouteon a fresh image with build tools but an unusual Node ABI).mcp_tool_audittable is empty.Fix
better-sqlite3import in a try/catch.node:sqlite(Node 22.5+).node:sqliteDatabaseSync in a thin adapter that exposes the sameAuditDatabaseinterface (prepare/pragma/close/open) used by the existing code, socloseAuditDband the WAL checkpoint path keep working without further changes.Test
Added
falls back to node:sqlite when better-sqlite3 binding is missingtoopen-sse/mcp-server/__tests__/audit.test.tsthat:better-sqlite3to throw the exact "Could not locate the bindings file" error from the published package,node:sqlitewith aDatabaseSyncstub,logToolCall→closeAuditDbcycle,exec("PRAGMA wal_checkpoint(TRUNCATE)")is called on the node:sqlite handle.Notes
release/v3.8.25(the version on the affected installs). Easy to cherry-pick tomain/release/v3.8.26if needed.open-sse/utils/cursorVersionDetector.ts:49andsrc/lib/copilot/codegraphKnowledge.ts:107, but those paths are not in the bug report — happy to send a follow-up if you want a single PR that covers all three.