diff --git a/src/main/codex/codex-session-resume-home.test.ts b/src/main/codex/codex-session-resume-home.test.ts index b17d1a19344..4f55dec236d 100644 --- a/src/main/codex/codex-session-resume-home.test.ts +++ b/src/main/codex/codex-session-resume-home.test.ts @@ -37,7 +37,7 @@ describe('resolveTrustedCodexSessionResumeHome', () => { }) it('rejects paths outside trusted homes or outside the rollout layout', () => { - const fileIsRegular = (): boolean => true + const fileIsRegular = vi.fn((): boolean => true) expect( resolveTrustedCodexSessionResumeHome({ transcriptPath: '/tmp/sessions/2026/07/20/rollout-a.jsonl', @@ -60,6 +60,7 @@ describe('resolveTrustedCodexSessionResumeHome', () => { fileIsRegular }) ).toBeNull() + expect(fileIsRegular).not.toHaveBeenCalled() }) it('rejects a trusted-looking path when the rollout no longer exists', () => { diff --git a/src/main/codex/codex-session-resume-home.ts b/src/main/codex/codex-session-resume-home.ts index be517792b7d..aebaf6a157c 100644 --- a/src/main/codex/codex-session-resume-home.ts +++ b/src/main/codex/codex-session-resume-home.ts @@ -48,15 +48,20 @@ function resolveTrustedCodexSessionResume(args: { fileIsRegular?: (filePath: string) => boolean }): { homePath: string; transcriptPath: string } | null { const persistedPath = args.transcriptPath?.trim() - const transcriptPath = persistedPath - ? resolveExistingRolloutPath(persistedPath, args.fileIsRegular ?? isRegularFile) - : null - if (!transcriptPath) { + if (!persistedPath) { return null } for (const homePath of args.trustedCodexHomes) { - if (isCodexRolloutInsideSessionsRoot(join(homePath, 'sessions'), transcriptPath)) { + const sessionsRoot = join(homePath, 'sessions') + if (!isCodexRolloutInsideSessionsRoot(sessionsRoot, persistedPath)) { + continue + } + const transcriptPath = resolveExistingRolloutPath( + persistedPath, + args.fileIsRegular ?? isRegularFile + ) + if (transcriptPath) { return { homePath, transcriptPath } } } diff --git a/src/main/ipc/pty.test.ts b/src/main/ipc/pty.test.ts index 50512b7f7df..cc7428dac3b 100644 --- a/src/main/ipc/pty.test.ts +++ b/src/main/ipc/pty.test.ts @@ -985,7 +985,8 @@ describe('registerPtyHandlers', () => { cols: 80, rows: 24, command: 'codex resume session-a', - env: { CODEX_HOME: '/custom/codex' }, + env: { CODEX_HOME: '/custom/codex', REMOVE_ME: 'stale' }, + envToDelete: ['CODEX_HOME', 'ORCA_CODEX_HOME', 'REMOVE_ME'], launchAgent: 'codex', resumeProviderSession: { key: 'session_id', @@ -998,6 +999,7 @@ describe('registerPtyHandlers', () => { expect(selectedHome).not.toHaveBeenCalled() expect(env.CODEX_HOME).toBe(systemHome) expect(env.ORCA_CODEX_HOME).toBe(systemHome) + expect(env.REMOVE_ME).toBeUndefined() }) it('does not fall back to the selected account when automatic resume provenance is rejected', async () => { @@ -1851,7 +1853,8 @@ describe('registerPtyHandlers', () => { cols: 80, rows: 24, command: 'codex resume session-a', - env: { CODEX_HOME: '/custom/codex' }, + env: { CODEX_HOME: '/custom/codex', REMOVE_ME: 'stale' }, + envToDelete: ['CODEX_HOME', 'ORCA_CODEX_HOME', 'REMOVE_ME'], launchAgent: 'codex', resumeProviderSession: { key: 'session_id', @@ -1864,6 +1867,68 @@ describe('registerPtyHandlers', () => { expect(selectedHome).not.toHaveBeenCalled() expect(env.CODEX_HOME).toBe(systemHome) expect(env.ORCA_CODEX_HOME).toBe(systemHome) + expect(env.REMOVE_ME).toBeUndefined() + }) + + it('keeps the authoritative home for runtime-created daemon resumes', async () => { + type RuntimeSpawnController = { + spawn(args: { + cols: number + rows: number + command: string + env: Record + envToDelete: string[] + launchAgent: 'codex' + resumeProviderSession: { + key: 'session_id' + id: string + transcriptPath: string + } + }): Promise<{ id: string }> + } + const daemonSpawn = setupDaemonAdapter() + const runtime = { + setPtyController: vi.fn(), + registerPty: vi.fn(), + noteTerminalSpawnCommand: vi.fn(), + onPtySpawned: vi.fn(), + onPtyExit: vi.fn(), + onPtyData: vi.fn() + } + const systemHome = '/Users/example/.codex' + handlers.clear() + registerPtyHandlers( + mainWindow as never, + runtime as never, + undefined, + undefined, + undefined, + undefined, + { prepareCodexSessionResume: async () => ({ codexHomePath: systemHome }) } + ) + const controller = runtime.setPtyController.mock.calls[0]?.[0] as RuntimeSpawnController + + await controller.spawn({ + cols: 80, + rows: 24, + command: 'codex resume session-a', + env: { CODEX_HOME: '/custom/codex', REMOVE_ME: 'stale' }, + envToDelete: ['CODEX_HOME', 'ORCA_CODEX_HOME', 'REMOVE_ME'], + launchAgent: 'codex', + resumeProviderSession: { + key: 'session_id', + id: 'session-a', + transcriptPath: `${systemHome}/sessions/2026/07/20/rollout-a.jsonl` + } + }) + + const spawnOptions = daemonSpawn.mock.calls.at(-1)?.[0] as DaemonSpawnCall + expect(spawnOptions.env.CODEX_HOME).toBe(systemHome) + expect(spawnOptions.env.ORCA_CODEX_HOME).toBe(systemHome) + expect(spawnOptions.env.REMOVE_ME).toBeUndefined() + expect(spawnOptions.envToDelete ?? []).not.toContain('CODEX_HOME') + expect(spawnOptions.envToDelete ?? []).not.toContain('ORCA_CODEX_HOME') + expect(spawnOptions.envToDelete).toContain('REMOVE_ME') }) it('prepares Codex project trust before a daemon-backed interactive launch', async () => { diff --git a/src/main/ipc/pty.ts b/src/main/ipc/pty.ts index f7568cfcc3a..5738485dfda 100644 --- a/src/main/ipc/pty.ts +++ b/src/main/ipc/pty.ts @@ -757,6 +757,12 @@ function mergePtyEnvDeletions( return Array.from(new Set([...(existingKeys ?? []), ...additionalKeys])) } +function removeCodexHomeDeletionRequests(keys: string[] | undefined): string[] | undefined { + // Why: resume provenance is launch-authoritative; late deletions must not fall back to the current account. + const filtered = keys?.filter((key) => key !== 'CODEX_HOME' && key !== 'ORCA_CODEX_HOME') + return filtered?.length ? filtered : undefined +} + function getInheritedAgentHookEnvKeysToDelete( spawnEnv: Record | undefined ): string[] { @@ -2922,6 +2928,9 @@ export function registerPtyHandlers( 'ORCA_CODEX_HOME' ]) } + if (codexResumeHome?.codexHomePath) { + spawnOptions.envToDelete = removeCodexHomeDeletionRequests(spawnOptions.envToDelete) + } deleteRequestedEnvKeys(env, spawnOptions.envToDelete) promoteAgentTeamsShimPath(env, requestedAgentTeamsPath) if (args.command !== undefined) { @@ -3830,7 +3839,7 @@ export function registerPtyHandlers( const envToDelete = claudeAuth?.stripAuthEnv ? [...CLAUDE_AUTH_ENV_VARS, 'ANTHROPIC_CUSTOM_HEADERS'] : undefined - const combinedEnvToDelete = mergePtyEnvDeletions( + let combinedEnvToDelete = mergePtyEnvDeletions( mergePtyEnvDeletions( mergePtyEnvDeletions( mergePtyEnvDeletions( @@ -3845,6 +3854,9 @@ export function registerPtyHandlers( // main cannot safely decide ownership for a process it may not parent. stripInheritedOrcaCodexHome ? ['ORCA_CODEX_HOME'] : [] ) + if (codexResumeHome?.codexHomePath) { + combinedEnvToDelete = removeCodexHomeDeletionRequests(combinedEnvToDelete) + } deleteRequestedEnvKeys(spawnEnv, combinedEnvToDelete) promoteAgentTeamsShimPath(spawnEnv, requestedAgentTeamsPath) const spawnOptions: PtySpawnOptions = {