fix(codex): keep automatic resume homes launch-authoritative

This commit is contained in:
Brennan Benson
2026-07-20 22:29:13 -07:00
parent 920cbdbe64
commit 0654c955de
3 changed files with 85 additions and 18 deletions
@@ -1,16 +1,27 @@
import { describe, expect, it } from 'vitest'
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
findTrustedCodexSessionResume,
resolveTrustedCodexSessionResumeHome
} from './codex-session-resume-home'
const tempRoots: string[] = []
afterEach(() => {
for (const root of tempRoots.splice(0)) {
rmSync(root, { recursive: true, force: true })
}
})
describe('resolveTrustedCodexSessionResumeHome', () => {
it('returns the trusted home containing a persisted rollout', () => {
expect(
resolveTrustedCodexSessionResumeHome({
transcriptPath: '/Users/example/.codex/sessions/2026/07/20/rollout-session.jsonl',
trustedCodexHomes: ['/managed/account/home', '/Users/example/.codex'],
fileExists: () => true
fileIsRegular: () => true
})
).toBe('/Users/example/.codex')
})
@@ -20,25 +31,33 @@ describe('resolveTrustedCodexSessionResumeHome', () => {
resolveTrustedCodexSessionResumeHome({
transcriptPath: 'C:\\Users\\Example\\.codex\\sessions\\2026\\07\\20\\rollout-a.jsonl',
trustedCodexHomes: ['c:\\users\\example\\.codex'],
fileExists: () => true
fileIsRegular: () => true
})
).toBe('c:\\users\\example\\.codex')
})
it('rejects paths outside trusted homes or outside the rollout layout', () => {
const fileExists = (): boolean => true
const fileIsRegular = (): boolean => true
expect(
resolveTrustedCodexSessionResumeHome({
transcriptPath: '/tmp/sessions/2026/07/20/rollout-a.jsonl',
trustedCodexHomes: ['/Users/example/.codex'],
fileExists
fileIsRegular
})
).toBeNull()
expect(
resolveTrustedCodexSessionResumeHome({
transcriptPath: '/Users/example/.codex/sessions/index.jsonl',
trustedCodexHomes: ['/Users/example/.codex'],
fileExists
fileIsRegular
})
).toBeNull()
expect(
resolveTrustedCodexSessionResumeHome({
transcriptPath:
'/Users/example/.codex/sessions/2026/07/20/rollout-a/../../../../outside.jsonl',
trustedCodexHomes: ['/Users/example/.codex'],
fileIsRegular
})
).toBeNull()
})
@@ -48,11 +67,34 @@ describe('resolveTrustedCodexSessionResumeHome', () => {
resolveTrustedCodexSessionResumeHome({
transcriptPath: '/Users/example/.codex/sessions/2026/07/20/rollout-a.jsonl',
trustedCodexHomes: ['/Users/example/.codex'],
fileExists: () => false
fileIsRegular: () => false
})
).toBeNull()
})
it('requires the transcript provenance to name a regular rollout file', () => {
const homePath = mkdtempSync(join(tmpdir(), 'orca-codex-resume-home-'))
tempRoots.push(homePath)
const rolloutDirectory = join(homePath, 'sessions', '2026', '07', '20', 'rollout-a.jsonl')
mkdirSync(rolloutDirectory, { recursive: true })
expect(
resolveTrustedCodexSessionResumeHome({
transcriptPath: rolloutDirectory,
trustedCodexHomes: [homePath]
})
).toBeNull()
const rolloutFile = join(homePath, 'sessions', '2026', '07', '20', 'rollout-b.jsonl')
writeFileSync(rolloutFile, '{}\n')
expect(
resolveTrustedCodexSessionResumeHome({
transcriptPath: rolloutFile,
trustedCodexHomes: [homePath]
})
).toBe(homePath)
})
it('finds older saved sessions by id when transcript provenance is absent', async () => {
const sessionId = '019f81b9-19a9-7651-a8d1-352d9420bd11'
const rolloutPath = `/managed/account/home/sessions/2026/07/20/rollout-2026-07-20T15-50-19-${sessionId}.jsonl`
@@ -72,6 +114,25 @@ describe('resolveTrustedCodexSessionResumeHome', () => {
).resolves.toEqual({ homePath: '/managed/account/home', transcriptPath: rolloutPath })
})
it('does not scan session trees when exact transcript provenance is valid', async () => {
const transcriptPath =
'/managed/account/home/sessions/2026/07/20/rollout-2026-07-20-session.jsonl'
const listSessionFiles = vi.fn((): AsyncIterable<string> => {
throw new Error('must not scan')
})
await expect(
findTrustedCodexSessionResume({
sessionId: 'session-a',
transcriptPath,
trustedCodexHomes: ['/managed/account/home'],
fileIsRegular: () => true,
listSessionFiles
})
).resolves.toEqual({ homePath: '/managed/account/home', transcriptPath })
expect(listSessionFiles).not.toHaveBeenCalled()
})
it('does not scan homes for an untrusted legacy session id shape', async () => {
const listSessionFiles = (): AsyncIterable<string> => {
throw new Error('must not scan')
+13 -5
View File
@@ -1,4 +1,4 @@
import { existsSync } from 'node:fs'
import { existsSync, lstatSync } from 'node:fs'
import { join } from 'node:path'
import {
getRuntimePathBasename,
@@ -7,15 +7,23 @@ import {
} from '../../shared/cross-platform-path'
import { listCodexSessionJsonlFilesIncrementally } from './codex-session-file-listing'
const ROLLOUT_RELATIVE_PATH = /^\d{4}\/\d{2}\/\d{2}\/rollout-.+\.jsonl$/
const ROLLOUT_RELATIVE_PATH = /^\d{4}\/\d{2}\/\d{2}\/rollout-[^/]+\.jsonl$/
function isRegularFile(filePath: string): boolean {
try {
return lstatSync(filePath).isFile()
} catch {
return false
}
}
export function resolveTrustedCodexSessionResumeHome(args: {
transcriptPath: string | undefined
trustedCodexHomes: readonly string[]
fileExists?: (filePath: string) => boolean
fileIsRegular?: (filePath: string) => boolean
}): string | null {
const transcriptPath = args.transcriptPath?.trim()
if (!transcriptPath || !(args.fileExists ?? existsSync)(transcriptPath)) {
if (!transcriptPath || !(args.fileIsRegular ?? isRegularFile)(transcriptPath)) {
return null
}
@@ -32,7 +40,7 @@ export async function findTrustedCodexSessionResume(args: {
sessionId: string
transcriptPath: string | undefined
trustedCodexHomes: readonly string[]
fileExists?: (filePath: string) => boolean
fileIsRegular?: (filePath: string) => boolean
listSessionFiles?: (sessionsRoot: string) => AsyncIterable<string>
}): Promise<{ homePath: string; transcriptPath: string } | null> {
const directHome = resolveTrustedCodexSessionResumeHome(args)
+4 -6
View File
@@ -797,10 +797,10 @@ async function prepareCodexSessionResumeForLaunch(args: {
if (args.target.runtime === 'wsl' || !codexRuntimeHome || !store) {
return null
}
const configuredSourceHome = resolveHostCodexSessionSourceHome(store.getSettings())
const systemHomePath = getSystemCodexHomePath()
// Why: codexSessionSourceHome is import-only; treating it as CODEX_HOME would mutate history sources and bypass account auth.
const trustedHomes = [
configuredSourceHome ?? systemHomePath,
systemHomePath,
...codexRuntimeHome.getHostCodexHomePathsForSessionDiscovery()
]
const sessionSource = await findTrustedCodexSessionResume({
@@ -821,12 +821,10 @@ async function prepareCodexSessionResumeForLaunch(args: {
},
{
isHostSystemDefaultRealHome: () => codexRuntimeHome!.isHostSystemDefaultRealHome(),
systemCodexHomePath: configuredSourceHome
systemCodexHomePath: systemHomePath
}
)
const resumeHome = migrated.useRealCodexHome
? (configuredSourceHome ?? systemHomePath)
: sessionSource.homePath
const resumeHome = migrated.useRealCodexHome ? systemHomePath : sessionSource.homePath
if (args.workspacePath) {
try {