@@ -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+ ] ) ;
4461const SHADOW_HOME_STATE_FILE_SET = new Set ( SHADOW_HOME_STATE_FILES ) ;
4562const SHADOW_HOME_CONFIG_FILE = "config.toml" ;
4663const SHADOW_HOME_SYNC_LOCK_DIR = ".codex-multi-auth-shadow-sync.lock" ;
@@ -90,6 +107,8 @@ const shadowHomeCleanupRetryMarkerDir =
90107let warnedInvalidRuntimeRotationProxyEnv = false ;
91108let warnedPendingAccountReadIdOverflow = false ;
92109let warnedShadowHomeSqliteLinkFailure = false ;
110+ const warnedShadowHomeLinkOnlyDirectoryFailures = new Set ( ) ;
111+ const warnedShadowHomeSqliteSidecarPlaceholderFailures = new Set ( ) ;
93112
94113async 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+
18691923function 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 / \. s q l i t e $ / i. test ( name ) ;
1949+ }
1950+
1951+ function isSqliteSidecarFile ( name ) {
1952+ return / \. s q l i t e - (?: s h m | w a l ) $ / 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 / ^ (?: s t a t e | l o g s ) _ \d + \. s q l i t e (?: - (?: s h m | w a l ) ) ? $ / . test ( normalizedName ) ;
1964+ }
1965+
1966+ function isCodexRuntimeTransientStateFile ( name ) {
1967+ const normalizedName = normalizeRuntimeShadowHomeEntryName ( name ) ;
1968+ return (
1969+ / ^ (?: a u t h | a c c o u n t s ) \. j s o n \. \d + \. [ a - z 0 - 9 ] + \. t m p $ / . test (
1970+ normalizedName ,
1971+ ) ||
1972+ / ^ \. c o d e x - g l o b a l - s t a t e \. j s o n \. t m p - [ a - z 0 - 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+
18931990function shouldMaterializeFileIntoShadowHome ( name ) {
1894- return / \. s q l i t e (?: - (?: s h m | w a l ) ) ? $ / 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+
19122089function 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+
22102418function 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