From 1e95c471a0b2cf80e503a53fdfba0f0654c2dfbc Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 9 Aug 2026 22:56:02 -0700 Subject: [PATCH] fix(state): read live WAL ownership safely Use WAL-aware read-only access when SQLite journal sidecars are live, while retaining immutable inspection for quiescent database families. This prevents false corruption during concurrent state initialization without mutating cold databases or changing the schema. --- src/state/openclaw-state-ownership.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/state/openclaw-state-ownership.ts b/src/state/openclaw-state-ownership.ts index 9b75a304ddcf..1332c34c2f7d 100644 --- a/src/state/openclaw-state-ownership.ts +++ b/src/state/openclaw-state-ownership.ts @@ -4,6 +4,7 @@ import type { DatabaseSync } from "node:sqlite"; import { isRecord } from "@openclaw/normalization-core/record-coerce"; import { isGatewayExternallySupervised } from "../infra/gateway-supervision.js"; import { openNodeSqliteDatabase, resolveImmutableSqliteFileUri } from "../infra/node-sqlite.js"; +import { OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "./openclaw-state-db-contract.js"; import { tableExists } from "./openclaw-state-db-schema-helpers.js"; export const STATE_SUPERVISION_KEY = "gateway.supervision"; @@ -113,7 +114,7 @@ export function inspectOpenClawStateOwnershipFromDatabase( return parseExternalOwnership(row.value_json, databasePath); } -/** Inspect one resolved state database path through a read-only connection. */ +/** Inspect one resolved state database path without mutating a quiescent SQLite family. */ export function inspectOpenClawStateOwnershipAtPath( databasePath: string, ): OpenClawExternalStateOwnership | null { @@ -121,11 +122,15 @@ export function inspectOpenClawStateOwnershipAtPath( if (!existsSync(resolvedPath)) { return null; } - const database = openNodeSqliteDatabase(resolveImmutableSqliteFileUri(resolvedPath), { - readOnly: true, - }); + const hasLiveJournal = ["-journal", "-shm", "-wal"].some((suffix) => + existsSync(`${resolvedPath}${suffix}`), + ); + const location = hasLiveJournal ? resolvedPath : resolveImmutableSqliteFileUri(resolvedPath); + const database = openNodeSqliteDatabase(location, { readOnly: true }); try { - database.exec("PRAGMA query_only = ON; PRAGMA trusted_schema = OFF;"); + database.exec( + `${hasLiveJournal ? `PRAGMA busy_timeout = ${OPENCLAW_SQLITE_BUSY_TIMEOUT_MS}; ` : ""}PRAGMA query_only = ON; PRAGMA trusted_schema = OFF;`, + ); return inspectOpenClawStateOwnershipFromDatabase(database, resolvedPath); } finally { database.close();