Skip to content

Commit 96e0e9b

Browse files
authored
Merge pull request #467 from ndycode/fix/runtime-shadow-goal-state-isolation
fix(shadow-home): isolate runtime goal state
2 parents d911bb0 + 9a81f73 commit 96e0e9b

2 files changed

Lines changed: 638 additions & 24 deletions

File tree

scripts/codex.js

Lines changed: 233 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,23 @@ const RUNTIME_ROTATION_SHADOW_HOME_OMIT_STATE_FILES = new Set([
4141
"auth.json",
4242
"accounts.json",
4343
]);
44+
const RUNTIME_ROTATION_SHADOW_HOME_OMIT_ROOT_DIRS = new Set(["multi-auth"]);
45+
const RUNTIME_ROTATION_SHADOW_HOME_LINK_ONLY_ROOT_DIRS = new Set([
46+
".sandbox",
47+
".sandbox-bin",
48+
".sandbox-secrets",
49+
".tmp",
50+
"ambient-suggestions",
51+
"archived_sessions",
52+
"backups",
53+
"cache",
54+
"generated_images",
55+
"log",
56+
"sqlite",
57+
"tmp",
58+
"understand-anything",
59+
"vendor_imports",
60+
]);
4461
const SHADOW_HOME_STATE_FILE_SET = new Set(SHADOW_HOME_STATE_FILES);
4562
const SHADOW_HOME_CONFIG_FILE = "config.toml";
4663
const SHADOW_HOME_SYNC_LOCK_DIR = ".codex-multi-auth-shadow-sync.lock";
@@ -90,6 +107,8 @@ const shadowHomeCleanupRetryMarkerDir =
90107
let warnedInvalidRuntimeRotationProxyEnv = false;
91108
let warnedPendingAccountReadIdOverflow = false;
92109
let warnedShadowHomeSqliteLinkFailure = false;
110+
const warnedShadowHomeLinkOnlyDirectoryFailures = new Set();
111+
const warnedShadowHomeSqliteSidecarPlaceholderFailures = new Set();
93112

94113
async function loadRuntimeConstants() {
95114
const fallback = {
@@ -1866,6 +1885,41 @@ function mirrorDirectoryIntoShadowHome(sourcePath, destinationPath) {
18661885
return "copied";
18671886
}
18681887

1888+
function linkDirectoryIntoShadowHome(sourcePath, destinationPath) {
1889+
try {
1890+
if ((process.env.CODEX_MULTI_AUTH_TEST_FORCE_SHADOW_DIR_COPY ?? "").trim() === "1") {
1891+
throw new Error("simulated directory link failure");
1892+
}
1893+
symlinkSync(
1894+
sourcePath,
1895+
destinationPath,
1896+
process.platform === "win32" ? "junction" : "dir",
1897+
);
1898+
return true;
1899+
} catch {
1900+
return false;
1901+
}
1902+
}
1903+
1904+
function warnSkippedLinkOnlyShadowHomeDirectory(name) {
1905+
if (warnedShadowHomeLinkOnlyDirectoryFailures.has(name)) {
1906+
return;
1907+
}
1908+
warnedShadowHomeLinkOnlyDirectoryFailures.add(name);
1909+
console.error(
1910+
`codex-multi-auth: skipped optional shadow-home directory ${name} because linking failed; refusing to copy generated runtime data.`,
1911+
);
1912+
}
1913+
1914+
function shouldCopyRuntimeGeneratedShadowHomeDirectoryFallback() {
1915+
const normalized = (
1916+
process.env.CODEX_MULTI_AUTH_RUNTIME_SHADOW_COPY_GENERATED_DIRS ?? ""
1917+
)
1918+
.trim()
1919+
.toLowerCase();
1920+
return normalized === "1" || normalized === "true" || normalized === "yes";
1921+
}
1922+
18691923
function linkFileIntoShadowHome(sourcePath, destinationPath) {
18701924
try {
18711925
symlinkSync(sourcePath, destinationPath, "file");
@@ -1890,25 +1944,148 @@ function mirrorFileIntoShadowHome(sourcePath, destinationPath, tightenFile) {
18901944
tightenFile(destinationPath);
18911945
}
18921946

1947+
function isSqliteMainFile(name) {
1948+
return /\.sqlite$/i.test(name);
1949+
}
1950+
1951+
function isSqliteSidecarFile(name) {
1952+
return /\.sqlite-(?:shm|wal)$/i.test(name);
1953+
}
1954+
1955+
function normalizeRuntimeShadowHomeEntryName(name) {
1956+
return process.platform === "win32" || process.platform === "darwin"
1957+
? name.toLowerCase()
1958+
: name;
1959+
}
1960+
1961+
function isCodexRuntimeLocalSqliteFile(name) {
1962+
const normalizedName = normalizeRuntimeShadowHomeEntryName(name);
1963+
return /^(?:state|logs)_\d+\.sqlite(?:-(?:shm|wal))?$/.test(normalizedName);
1964+
}
1965+
1966+
function isCodexRuntimeTransientStateFile(name) {
1967+
const normalizedName = normalizeRuntimeShadowHomeEntryName(name);
1968+
return (
1969+
/^(?:auth|accounts)\.json\.\d+\.[a-z0-9]+\.tmp$/.test(
1970+
normalizedName,
1971+
) ||
1972+
/^\.codex-global-state\.json\.tmp-[a-z0-9-]+$/.test(normalizedName)
1973+
);
1974+
}
1975+
1976+
function isRuntimeRotationShadowHomeOmittedEntry(name) {
1977+
const normalizedName = normalizeRuntimeShadowHomeEntryName(name);
1978+
return (
1979+
RUNTIME_ROTATION_SHADOW_HOME_OMIT_ROOT_DIRS.has(normalizedName) ||
1980+
isCodexRuntimeLocalSqliteFile(name) ||
1981+
isCodexRuntimeTransientStateFile(name)
1982+
);
1983+
}
1984+
1985+
function isRuntimeRotationShadowHomeLinkOnlyDirectory(name) {
1986+
const normalizedName = normalizeRuntimeShadowHomeEntryName(name);
1987+
return RUNTIME_ROTATION_SHADOW_HOME_LINK_ONLY_ROOT_DIRS.has(normalizedName);
1988+
}
1989+
18931990
function shouldMaterializeFileIntoShadowHome(name) {
1894-
return /\.sqlite(?:-(?:shm|wal))?$/i.test(name);
1991+
return isSqliteMainFile(name) || isSqliteSidecarFile(name);
18951992
}
18961993

1897-
function materializeFileIntoShadowHome(sourcePath, destinationPath) {
1994+
function warnSkippedSqliteShadowHomeMaterialization() {
1995+
if (!warnedShadowHomeSqliteLinkFailure) {
1996+
warnedShadowHomeSqliteLinkFailure = true;
1997+
console.error(
1998+
"codex-multi-auth: skipped SQLite shadow-home materialization because linking failed; refusing to copy active SQLite state.",
1999+
);
2000+
}
2001+
}
2002+
2003+
function warnSkippedSqliteShadowHomeSidecarPlaceholder(error, destinationPath) {
2004+
if (!warnedShadowHomeSqliteSidecarPlaceholderFailures.has(destinationPath)) {
2005+
warnedShadowHomeSqliteSidecarPlaceholderFailures.add(destinationPath);
2006+
console.error(
2007+
`codex-multi-auth: skipped SQLite shadow-home sidecar placeholder for ${destinationPath} because linking failed: ${error instanceof Error ? error.message : String(error)}`,
2008+
);
2009+
}
2010+
}
2011+
2012+
function materializeSqliteSidecarPlaceholder(sourceSidecarPath, destinationSidecarPath) {
18982013
try {
1899-
linkSync(sourcePath, destinationPath);
2014+
if (
2015+
(process.env.CODEX_MULTI_AUTH_TEST_FORCE_SHADOW_SIDECAR_PLACEHOLDER_FAILURE ??
2016+
""
2017+
).trim() === "1"
2018+
) {
2019+
const error = new Error("simulated SQLite sidecar placeholder failure");
2020+
error.code = "EPERM";
2021+
throw error;
2022+
}
2023+
symlinkSync(sourceSidecarPath, destinationSidecarPath, "file");
19002024
return true;
1901-
} catch {
1902-
if (!warnedShadowHomeSqliteLinkFailure) {
1903-
warnedShadowHomeSqliteLinkFailure = true;
1904-
console.error(
1905-
"codex-multi-auth: skipped SQLite shadow-home materialization because hard-linking failed; refusing to copy active SQLite state.",
1906-
);
2025+
} catch (error) {
2026+
if (error?.code === "EEXIST") {
2027+
return true;
2028+
}
2029+
warnSkippedSqliteShadowHomeSidecarPlaceholder(error, destinationSidecarPath);
2030+
return false;
2031+
}
2032+
}
2033+
2034+
function materializeFileIntoShadowHome(sourcePath, destinationPath) {
2035+
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+
}
2044+
if (linkFileIntoShadowHome(sourcePath, destinationPath)) {
2045+
return true;
19072046
}
2047+
warnSkippedSqliteShadowHomeMaterialization();
2048+
} catch {
2049+
warnSkippedSqliteShadowHomeMaterialization();
19082050
}
19092051
return false;
19102052
}
19112053

2054+
function materializeSqliteSidecarsIntoShadowHome(sourcePath, destinationPath) {
2055+
for (const suffix of ["-wal", "-shm"]) {
2056+
const sourceSidecarPath = `${sourcePath}${suffix}`;
2057+
const destinationSidecarPath = `${destinationPath}${suffix}`;
2058+
if (existsSync(destinationSidecarPath)) {
2059+
continue;
2060+
}
2061+
if (existsSync(sourceSidecarPath)) {
2062+
if (!materializeFileIntoShadowHome(sourceSidecarPath, destinationSidecarPath)) {
2063+
if (!materializeSqliteSidecarPlaceholder(
2064+
sourceSidecarPath,
2065+
destinationSidecarPath,
2066+
)) {
2067+
return false;
2068+
}
2069+
}
2070+
continue;
2071+
}
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+
// Best-effort cleanup; keep removing the remaining SQLite siblings.
2085+
}
2086+
}
2087+
}
2088+
19122089
function collectShadowHomeSyncFileNames(shadowCodexHome, syncFileNames) {
19132090
try {
19142091
for (const entry of readdirSync(shadowCodexHome, { withFileTypes: true })) {
@@ -2018,9 +2195,14 @@ function syncAdditionalShadowHomeFiles(
20182195
originalFileStates,
20192196
tightenFile,
20202197
skipSyncBackNames = new Set(),
2198+
skipSyncBackPredicate = () => false,
20212199
) {
20222200
for (const name of names) {
2023-
if (SHADOW_HOME_STATE_FILE_SET.has(name) || skipSyncBackNames.has(name)) {
2201+
if (
2202+
SHADOW_HOME_STATE_FILE_SET.has(name) ||
2203+
skipSyncBackNames.has(name) ||
2204+
skipSyncBackPredicate(name)
2205+
) {
20242206
continue;
20252207
}
20262208
const shadowPath = join(shadowCodexHome, name);
@@ -2056,6 +2238,10 @@ function createShadowHomeMirror(
20562238
) {
20572239
const syncFileNames = new Set(SHADOW_HOME_STATE_FILES);
20582240
const skipSyncBackNames = new Set(options.skipSyncBackNames ?? []);
2241+
const skipMirrorPredicate = options.skipMirrorPredicate ?? (() => false);
2242+
const skipSyncBackPredicate = options.skipSyncBackPredicate ?? (() => false);
2243+
const linkOnlyDirectoryPredicate =
2244+
options.linkOnlyDirectoryPredicate ?? (() => false);
20592245
const originalFileStates = new Map();
20602246
const copiedDirectoryNames = new Set();
20612247
const rememberSyncFile = (name) => {
@@ -2077,7 +2263,8 @@ function createShadowHomeMirror(
20772263
if (
20782264
name === SHADOW_HOME_CONFIG_FILE ||
20792265
name === SHADOW_HOME_SYNC_STATE_FILE ||
2080-
name === SHADOW_HOME_SYNC_LOCK_DIR
2266+
name === SHADOW_HOME_SYNC_LOCK_DIR ||
2267+
skipMirrorPredicate(name)
20812268
) {
20822269
continue;
20832270
}
@@ -2101,6 +2288,15 @@ function createShadowHomeMirror(
21012288
throw new Error(`Expected ${name} to be a file`);
21022289
}
21032290
if (directoryLike) {
2291+
if (
2292+
linkOnlyDirectoryPredicate(name) &&
2293+
!shouldCopyRuntimeGeneratedShadowHomeDirectoryFallback()
2294+
) {
2295+
if (!linkDirectoryIntoShadowHome(sourcePath, destinationPath)) {
2296+
warnSkippedLinkOnlyShadowHomeDirectory(name);
2297+
}
2298+
continue;
2299+
}
21042300
if (mirrorDirectoryIntoShadowHome(sourcePath, destinationPath) === "copied") {
21052301
copiedDirectoryNames.add(name);
21062302
}
@@ -2112,7 +2308,14 @@ function createShadowHomeMirror(
21122308
copyFileSync(sourcePath, destinationPath);
21132309
tightenFile(destinationPath);
21142310
} else if (shouldMaterializeFile) {
2115-
materializeFileIntoShadowHome(sourcePath, destinationPath);
2311+
if (isSqliteSidecarFile(name)) {
2312+
continue;
2313+
}
2314+
if (materializeFileIntoShadowHome(sourcePath, destinationPath) && isSqliteMainFile(name)) {
2315+
if (!materializeSqliteSidecarsIntoShadowHome(sourcePath, destinationPath)) {
2316+
removeSqliteShadowHomeMaterialization(destinationPath);
2317+
}
2318+
}
21162319
} else {
21172320
mirrorFileIntoShadowHome(sourcePath, destinationPath, tightenFile);
21182321
}
@@ -2151,6 +2354,7 @@ function createShadowHomeMirror(
21512354
originalFileStates,
21522355
tightenFile,
21532356
skipSyncBackNames,
2357+
skipSyncBackPredicate,
21542358
);
21552359
} catch {
21562360
// Best-effort only; runtime auth refreshes should not fail cleanup.
@@ -2207,6 +2411,10 @@ function resolveOriginalMultiAuthDir(env) {
22072411
return undefined;
22082412
}
22092413

2414+
function resolveRuntimeRotationOriginalMultiAuthDir(originalCodexHome, env) {
2415+
return resolveOriginalMultiAuthDir(env) ?? join(originalCodexHome, "multi-auth");
2416+
}
2417+
22102418
function parseRuntimeRotationProxyEnv(value) {
22112419
if (value === undefined) return undefined;
22122420
const normalized = value.trim().toLowerCase();
@@ -2316,7 +2524,10 @@ function createRuntimeRotationProxyCodexHome(
23162524
shadowCodexHome,
23172525
tightenShadowHomePermissions,
23182526
{
2527+
skipMirrorPredicate: isRuntimeRotationShadowHomeOmittedEntry,
23192528
skipSyncBackNames: RUNTIME_ROTATION_SHADOW_HOME_OMIT_STATE_FILES,
2529+
skipSyncBackPredicate: isRuntimeRotationShadowHomeOmittedEntry,
2530+
linkOnlyDirectoryPredicate: isRuntimeRotationShadowHomeLinkOnlyDirectory,
23202531
},
23212532
);
23222533
omitRuntimeRotationShadowHomeStateFiles(shadowCodexHome);
@@ -2341,11 +2552,11 @@ function createRuntimeRotationProxyCodexHome(
23412552
...baseEnv,
23422553
CODEX_HOME: shadowCodexHome,
23432554
OPENAI_API_KEY: clientApiKey,
2555+
CODEX_MULTI_AUTH_DIR: resolveRuntimeRotationOriginalMultiAuthDir(
2556+
originalCodexHome,
2557+
baseEnv,
2558+
),
23442559
};
2345-
const originalMultiAuthDir = resolveOriginalMultiAuthDir(baseEnv);
2346-
if (originalMultiAuthDir) {
2347-
forwardedEnv.CODEX_MULTI_AUTH_DIR = originalMultiAuthDir;
2348-
}
23492560

23502561
return {
23512562
env: forwardedEnv,
@@ -2800,6 +3011,10 @@ function startRuntimeRotationAppHelper(baseContext) {
28003011
{
28013012
env: {
28023013
...baseContext.env,
3014+
CODEX_MULTI_AUTH_DIR: resolveRuntimeRotationOriginalMultiAuthDir(
3015+
realCodexHome,
3016+
baseContext.env,
3017+
),
28033018
[APP_RUNTIME_HELPER_OWNER_PID_ENV]: String(process.pid),
28043019
[APP_RUNTIME_HELPER_REAL_CODEX_HOME_ENV]: realCodexHome,
28053020
},
@@ -3528,6 +3743,8 @@ function repairCodexSessionIndex(codexHome) {
35283743

35293744
const additions = [];
35303745
for (const rolloutPath of collectRolloutFiles(sessionsDir)) {
3746+
const idFromName = extractRolloutIdFromFilename(basename(rolloutPath));
3747+
if (idFromName && seen.has(idFromName)) continue;
35313748
const entry = parseRolloutIndexEntry(rolloutPath);
35323749
if (!entry || seen.has(entry.id)) continue;
35333750
seen.add(entry.id);

0 commit comments

Comments
 (0)