fix(codex): guard the daemon socket in the home a resumed Codex pane launches in (#23724)

This commit is contained in:
Brennan Benson
2026-09-29 00:36:07 -07:00
committed by GitHub
parent f4829f0363
commit b8a7dafafc
2 changed files with 150 additions and 0 deletions
@@ -0,0 +1,145 @@
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { VerifiedCodexResumeSource } from '../codex/codex-session-resume-preparation'
import {
CODEX_DAEMON_OVERRIDE_MARKER,
codexDaemonSocketPathExceedsLimit
} from '../codex/codex-daemon-socket-path-guard'
const mocks = vi.hoisted(() => ({
hooksEnabled: false,
systemHomePath: '',
sharedHomePath: '',
installForLaunchPrep: vi.fn(),
refreshRuntimeUserHooksForLaunchPrep: vi.fn(),
ensureRealHomeCodexHookState: vi.fn(async () => {}),
prepareCodexSessionResume: vi.fn(),
prepareLegacySharedCodexSessionResume: vi.fn()
}))
vi.mock('electron', () => ({ app: { getPath: vi.fn(() => '/tmp/orca-user-data') } }))
vi.mock('../agent-trust-presets', () => ({ markCodexProjectTrusted: async () => {} }))
vi.mock('../codex/hook-service', () => ({
codexHookService: {
installForLaunchPrep: mocks.installForLaunchPrep,
refreshRuntimeUserHooksForLaunchPrep: mocks.refreshRuntimeUserHooksForLaunchPrep
}
}))
vi.mock('../codex/codex-real-home-hook-install', () => ({
ensureRealHomeCodexHookState: mocks.ensureRealHomeCodexHookState
}))
vi.mock('../agent-hooks/managed-agent-hook-controls', () => ({
isAgentStatusHooksEnabled: () => mocks.hooksEnabled
}))
vi.mock('../codex/codex-home-paths', async (importOriginal) => ({
...(await importOriginal<object>()),
getSystemCodexHomePath: () => mocks.systemHomePath,
getOrcaManagedCodexHomePath: () => mocks.sharedHomePath
}))
vi.mock('../codex/codex-session-resume-preparation', () => ({
prepareCodexSessionResume: mocks.prepareCodexSessionResume
}))
vi.mock('../codex/codex-legacy-session-resume', () => ({
prepareLegacySharedCodexSessionResume: mocks.prepareLegacySharedCodexSessionResume
}))
vi.mock('./main-process-state', () => ({
mainProcessState: {
codexRuntimeHome: {
isHostSystemDefaultRealHome: () => false,
getHostCodexHomePathsForSessionDiscovery: () => [],
resolveSelectedHostAccountCodexHomePathForResume: () => null
},
store: { getSettings: () => ({}) }
}
}))
import { prepareCodexSessionResumeForLaunch } from './codex-session-resume-launch'
// Why: long enough that the daemon socket overflows sun_path on every host OS.
const LONG_SEGMENT = 'a'.repeat(60)
describe('Codex session resume daemon socket guard', () => {
let root: string
let accountHome: string
let warn: ReturnType<typeof vi.spyOn>
beforeEach(() => {
vi.clearAllMocks()
root = mkdtempSync(join(tmpdir(), 'orca-resume-guard-'))
accountHome = join(root, 'codex-accounts', LONG_SEGMENT, 'home')
mocks.systemHomePath = join(root, 'system', LONG_SEGMENT, '.codex')
mocks.sharedHomePath = join(root, 'codex-runtime-home', 'home')
for (const home of [accountHome, mocks.systemHomePath, mocks.sharedHomePath]) {
mkdirSync(home, { recursive: true })
}
mocks.hooksEnabled = false
mocks.prepareLegacySharedCodexSessionResume.mockResolvedValue({ useRealCodexHome: false })
mocks.prepareCodexSessionResume.mockImplementation(
async (args: {
resolveVerifiedResumeHome: (source: VerifiedCodexResumeSource) => Promise<string>
}) => ({
outcome: 'resume' as const,
codexHomePath: await args.resolveVerifiedResumeHome({
homePath: accountHome,
transcriptPath: join(accountHome, 'sessions', 'abc.jsonl')
})
})
)
warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
})
afterEach(() => {
warn.mockRestore()
rmSync(root, { recursive: true, force: true })
})
function resume(): ReturnType<typeof prepareCodexSessionResumeForLaunch> {
return prepareCodexSessionResumeForLaunch({
providerSession: { key: 'session_id', id: 'abc' },
target: { runtime: 'host' },
workspacePath: join(root, 'workspace')
})
}
it('guards the resumed account home when hook repair fails before mirroring config', async () => {
expect(codexDaemonSocketPathExceedsLimit(accountHome)).toBe(true)
writeFileSync(join(accountHome, 'config.toml'), 'model = "gpt-5"\n', 'utf-8')
mocks.hooksEnabled = true
mocks.installForLaunchPrep.mockRejectedValue(new Error('Could not parse Codex hooks.json'))
const preparation = await resume()
expect(preparation).toMatchObject({ outcome: 'resume', codexHomePath: accountHome })
const config = readFileSync(join(accountHome, 'config.toml'), 'utf-8')
expect(config).toContain('model = "gpt-5"')
expect(config).toContain(`daemon_auto_start = false ${CODEX_DAEMON_OVERRIDE_MARKER}`)
})
it('guards the resumed account home when hooks are off and the refresh returns early', async () => {
mocks.refreshRuntimeUserHooksForLaunchPrep.mockResolvedValue({
agent: 'codex',
state: 'error',
detail: 'Could not read system Codex hooks.json'
})
await resume()
expect(mocks.refreshRuntimeUserHooksForLaunchPrep).toHaveBeenCalledWith(accountHome)
expect(readFileSync(join(accountHome, 'config.toml'), 'utf-8')).toContain(
`daemon_auto_start = false ${CODEX_DAEMON_OVERRIDE_MARKER}`
)
})
it('never writes the guard into the real Codex home a migrated resume runs in', async () => {
expect(codexDaemonSocketPathExceedsLimit(mocks.systemHomePath)).toBe(true)
mocks.prepareLegacySharedCodexSessionResume.mockResolvedValue({ useRealCodexHome: true })
const preparation = await resume()
expect(preparation).toMatchObject({ codexHomePath: mocks.systemHomePath })
expect(mocks.ensureRealHomeCodexHookState).toHaveBeenCalledTimes(1)
expect(existsSync(join(mocks.systemHomePath, 'config.toml'))).toBe(false)
})
})
@@ -7,6 +7,7 @@ import { prepareLegacySharedCodexSessionResume } from '../codex/codex-legacy-ses
import { ManagedCodexHomeTemporarilyUnavailableError } from '../codex-accounts/host-codex-managed-home-ownership'
import { codexHookService } from '../codex/hook-service'
import { ensureRealHomeCodexHookState } from '../codex/codex-real-home-hook-install'
import { ensureCodexDaemonSocketGuard } from '../codex/codex-config-mirror'
import { isAgentStatusHooksEnabled } from '../agent-hooks/managed-agent-hook-controls'
import { markCodexProjectTrusted } from '../agent-trust-presets'
import { awaitAgentTrustWriteWithinDeadline } from '../agent-trust-write-deadline'
@@ -111,6 +112,10 @@ export async function prepareCodexSessionResumeForLaunch(args: {
// Why: hook repair is best-effort; session provenance must still win over the currently selected home.
console.warn('[codex-hook-service] failed to prepare automatic resume home:', error)
}
if (!isSystemHome) {
// Why: this pins the resumed pane's CODEX_HOME, and hook repair above can skip or fail before its config mirror applies the daemon guard.
ensureCodexDaemonSocketGuard(resumeHome)
}
return resumeHome
}
})