Skip to content

Commit e6c9cb1

Browse files
fix: keep sqlite shadow mirrors atomic
Avoid leaving partial SQLite main/WAL/SHM mirrors when runtime shadow-home sidecar materialization fails. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 0de3ee5 commit e6c9cb1

2 files changed

Lines changed: 92 additions & 14 deletions

File tree

scripts/codex.js

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2021,15 +2021,26 @@ function materializeSqliteSidecarPlaceholder(sourceSidecarPath, destinationSidec
20212021
throw error;
20222022
}
20232023
symlinkSync(sourceSidecarPath, destinationSidecarPath, "file");
2024+
return true;
20242025
} catch (error) {
2025-
if (error?.code !== "EEXIST") {
2026-
warnSkippedSqliteShadowHomeSidecarPlaceholder(error, destinationSidecarPath);
2026+
if (error?.code === "EEXIST") {
2027+
return true;
20272028
}
2029+
warnSkippedSqliteShadowHomeSidecarPlaceholder(error, destinationSidecarPath);
2030+
return false;
20282031
}
20292032
}
20302033

20312034
function materializeFileIntoShadowHome(sourcePath, destinationPath) {
20322035
try {
2036+
if (
2037+
(process.env.CODEX_MULTI_AUTH_TEST_FORCE_SHADOW_SQLITE_SIDECAR_LINK_FAILURE ??
2038+
""
2039+
).trim() === "1" &&
2040+
isSqliteSidecarFile(basename(sourcePath))
2041+
) {
2042+
throw new Error("simulated SQLite sidecar link failure");
2043+
}
20332044
if (linkFileIntoShadowHome(sourcePath, destinationPath)) {
20342045
return true;
20352046
}
@@ -2049,14 +2060,28 @@ function materializeSqliteSidecarsIntoShadowHome(sourcePath, destinationPath) {
20492060
}
20502061
if (existsSync(sourceSidecarPath)) {
20512062
if (!materializeFileIntoShadowHome(sourceSidecarPath, destinationSidecarPath)) {
2052-
materializeSqliteSidecarPlaceholder(
2063+
if (!materializeSqliteSidecarPlaceholder(
20532064
sourceSidecarPath,
20542065
destinationSidecarPath,
2055-
);
2066+
)) {
2067+
return false;
2068+
}
20562069
}
20572070
continue;
20582071
}
2059-
materializeSqliteSidecarPlaceholder(sourceSidecarPath, destinationSidecarPath);
2072+
if (!materializeSqliteSidecarPlaceholder(sourceSidecarPath, destinationSidecarPath)) {
2073+
return false;
2074+
}
2075+
}
2076+
return true;
2077+
}
2078+
2079+
function removeSqliteShadowHomeMaterialization(destinationPath) {
2080+
for (const path of [destinationPath, `${destinationPath}-wal`, `${destinationPath}-shm`]) {
2081+
try {
2082+
rmSync(path, { force: true });
2083+
} catch {
2084+
}
20602085
}
20612086
}
20622087

@@ -2282,8 +2307,13 @@ function createShadowHomeMirror(
22822307
copyFileSync(sourcePath, destinationPath);
22832308
tightenFile(destinationPath);
22842309
} else if (shouldMaterializeFile) {
2310+
if (isSqliteSidecarFile(name)) {
2311+
continue;
2312+
}
22852313
if (materializeFileIntoShadowHome(sourcePath, destinationPath) && isSqliteMainFile(name)) {
2286-
materializeSqliteSidecarsIntoShadowHome(sourcePath, destinationPath);
2314+
if (!materializeSqliteSidecarsIntoShadowHome(sourcePath, destinationPath)) {
2315+
removeSqliteShadowHomeMaterialization(destinationPath);
2316+
}
22872317
}
22882318
} else {
22892319
mirrorFileIntoShadowHome(sourcePath, destinationPath, tightenFile);

test/codex-bin-wrapper.test.ts

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,9 +1068,7 @@ describe("codex bin wrapper", () => {
10681068
'const cacheShmPath = path.join(process.env.CODEX_HOME ?? "", "plugin_cache.sqlite-shm");',
10691069
'console.log(`CACHE_SQLITE_MIRRORED:${fs.existsSync(cachePath)}`);',
10701070
'console.log(`CACHE_WAL_MIRRORED:${fs.existsSync(cacheWalPath)}`);',
1071-
'let cacheShmPlaceholder = "false";',
1072-
'try { cacheShmPlaceholder = String(fs.lstatSync(cacheShmPath).isSymbolicLink()); } catch { cacheShmPlaceholder = process.platform === "win32" ? "skipped" : "false"; }',
1073-
'console.log(`CACHE_SHM_PLACEHOLDER:${cacheShmPlaceholder}`);',
1071+
'console.log(`CACHE_SHM_MIRRORED:${fs.existsSync(cacheShmPath)}`);',
10741072
'const logPath = path.join(process.env.CODEX_HOME ?? "", "logs_2.sqlite");',
10751073
'const logWalPath = path.join(process.env.CODEX_HOME ?? "", "logs_2.sqlite-wal");',
10761074
'const logShmPath = path.join(process.env.CODEX_HOME ?? "", "logs_2.sqlite-shm");',
@@ -1176,6 +1174,7 @@ describe("codex bin wrapper", () => {
11761174
writeFileSync(join(originalHome, "LOGS_3.sqlite"), "upper log\n", "utf8");
11771175
writeFileSync(join(originalHome, "plugin_cache.sqlite"), "cache\n", "utf8");
11781176
writeFileSync(join(originalHome, "plugin_cache.sqlite-wal"), "cache wal\n", "utf8");
1177+
writeFileSync(join(originalHome, "plugin_cache.sqlite-shm"), "cache shm\n", "utf8");
11791178
writeFileSync(
11801179
join(originalHome, "config.toml"),
11811180
[
@@ -1228,7 +1227,7 @@ describe("codex bin wrapper", () => {
12281227
expect(output).toContain("GLOBAL_STATE_TMP_ISOLATED:true");
12291228
expect(output).toContain("CACHE_SQLITE_MIRRORED:true");
12301229
expect(output).toContain("CACHE_WAL_MIRRORED:true");
1231-
expect(output).toMatch(/^CACHE_SHM_PLACEHOLDER:(?:true|skipped)$/m);
1230+
expect(output).toContain("CACHE_SHM_MIRRORED:true");
12321231
expect(output).toContain("LOG_SQLITE_MIRRORED:false");
12331232
expect(output).toContain("LOG_WAL_MIRRORED:false");
12341233
expect(output).toContain("LOG_SHM_MIRRORED:false");
@@ -1368,10 +1367,57 @@ describe("codex bin wrapper", () => {
13681367
"#!/usr/bin/env node",
13691368
'const fs = require("node:fs");',
13701369
'const path = require("node:path");',
1370+
'const cachePath = path.join(process.env.CODEX_HOME ?? "", "plugin_cache.sqlite");',
1371+
'const cacheWalPath = path.join(process.env.CODEX_HOME ?? "", "plugin_cache.sqlite-wal");',
13711372
'const cacheShmPath = path.join(process.env.CODEX_HOME ?? "", "plugin_cache.sqlite-shm");',
1372-
'let cacheShmPlaceholder = "false";',
1373-
'try { cacheShmPlaceholder = String(fs.lstatSync(cacheShmPath).isSymbolicLink()); } catch { cacheShmPlaceholder = "false"; }',
1374-
'console.log(`CACHE_SHM_PLACEHOLDER:${cacheShmPlaceholder}`);',
1373+
'console.log(`CACHE_SQLITE_MIRRORED:${fs.existsSync(cachePath)}`);',
1374+
'console.log(`CACHE_WAL_MIRRORED:${fs.existsSync(cacheWalPath)}`);',
1375+
'console.log(`CACHE_SHM_MIRRORED:${fs.existsSync(cacheShmPath)}`);',
1376+
"process.exit(0);",
1377+
]);
1378+
const originalHome = join(fixtureRoot, "codex-home");
1379+
const markerPath = join(fixtureRoot, "proxy-marker.txt");
1380+
mkdirSync(originalHome, { recursive: true });
1381+
writeFileSync(join(originalHome, "plugin_cache.sqlite"), "cache\n", "utf8");
1382+
writeFileSync(join(originalHome, "plugin_cache.sqlite-wal"), "cache wal\n", "utf8");
1383+
writeFileSync(join(originalHome, "plugin_cache.sqlite-shm"), "cache shm\n", "utf8");
1384+
1385+
const result = runWrapper(fixtureRoot, ["exec", "status"], {
1386+
CODEX_MULTI_AUTH_REAL_CODEX_BIN: fakeBin,
1387+
CODEX_HOME: originalHome,
1388+
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY: "1",
1389+
CODEX_MULTI_AUTH_TEST_PROXY_BASE_URL: "http://127.0.0.1:4567",
1390+
CODEX_MULTI_AUTH_TEST_PROXY_MARKER: markerPath,
1391+
CODEX_MULTI_AUTH_TEST_FORCE_SHADOW_SQLITE_SIDECAR_LINK_FAILURE: "1",
1392+
CODEX_MULTI_AUTH_TEST_FORCE_SHADOW_SIDECAR_PLACEHOLDER_FAILURE: "1",
1393+
OPENAI_API_KEY: undefined,
1394+
});
1395+
1396+
const output = combinedOutput(result);
1397+
expect(result.status).toBe(0);
1398+
expect(output).toContain("CACHE_SQLITE_MIRRORED:false");
1399+
expect(output).toContain("CACHE_WAL_MIRRORED:false");
1400+
expect(output).toContain("CACHE_SHM_MIRRORED:false");
1401+
expect(output).toContain(
1402+
"codex-multi-auth: skipped SQLite shadow-home sidecar placeholder for",
1403+
);
1404+
expect(output).toContain("plugin_cache.sqlite-wal");
1405+
expect(output).toContain("simulated SQLite sidecar placeholder failure");
1406+
});
1407+
1408+
it("removes sqlite materialization when a missing sidecar placeholder fails", () => {
1409+
const fixtureRoot = createWrapperFixture();
1410+
createRuntimeRotationProxyFixtureModule(fixtureRoot);
1411+
const fakeBin = createCustomFakeCodexBin(fixtureRoot, [
1412+
"#!/usr/bin/env node",
1413+
'const fs = require("node:fs");',
1414+
'const path = require("node:path");',
1415+
'const cachePath = path.join(process.env.CODEX_HOME ?? "", "plugin_cache.sqlite");',
1416+
'const cacheWalPath = path.join(process.env.CODEX_HOME ?? "", "plugin_cache.sqlite-wal");',
1417+
'const cacheShmPath = path.join(process.env.CODEX_HOME ?? "", "plugin_cache.sqlite-shm");',
1418+
'console.log(`CACHE_SQLITE_MIRRORED:${fs.existsSync(cachePath)}`);',
1419+
'console.log(`CACHE_WAL_MIRRORED:${fs.existsSync(cacheWalPath)}`);',
1420+
'console.log(`CACHE_SHM_MIRRORED:${fs.existsSync(cacheShmPath)}`);',
13751421
"process.exit(0);",
13761422
]);
13771423
const originalHome = join(fixtureRoot, "codex-home");
@@ -1392,7 +1438,9 @@ describe("codex bin wrapper", () => {
13921438

13931439
const output = combinedOutput(result);
13941440
expect(result.status).toBe(0);
1395-
expect(output).toContain("CACHE_SHM_PLACEHOLDER:false");
1441+
expect(output).toContain("CACHE_SQLITE_MIRRORED:false");
1442+
expect(output).toContain("CACHE_WAL_MIRRORED:false");
1443+
expect(output).toContain("CACHE_SHM_MIRRORED:false");
13961444
expect(output).toContain(
13971445
"codex-multi-auth: skipped SQLite shadow-home sidecar placeholder for",
13981446
);

0 commit comments

Comments
 (0)