fix(codex): a real-home resume starts at once, without waiting for approval

A resume into the real ~/.codex waited until the background approval settled,
up to its 30 s deadline on a cold app-server: bookkeeping for later launches
gating the resume the user asked for. It now starts at once. If the approval is
still running, that first resume can show Codex's hook review once; the
approval then lands and later resumes and plain codex launches are trusted.

Trusting Orca's entries per process was the alternative, but the resume command
is typed into the pane's shell, and hook settings stay out of typed commands.
This commit is contained in:
Brennan Benson
2026-09-28 22:52:00 -07:00
parent 09abc64258
commit 5a737261d8
5 changed files with 5 additions and 59 deletions
@@ -116,16 +116,6 @@ export function ensureRealHomeCodexHookState(args: {
return ensureInFlight
}
/**
* For a resume that must run in the real home, with no managed home to fall
* back to: waits until a background grant settles, which its deadline bounds.
* Settled means Codex approved the entry or the grant withdrew it.
*/
export async function awaitRealHomeCodexHookTrust(): Promise<RealHomeCodexHookLane> {
await backgroundGrant
return currentLane
}
async function runRealHomeCodexHookEnsure(args: {
hooksEnabled: boolean
userDataPath: string
@@ -34,7 +34,6 @@ vi.mock('../codex-cli/command', () => ({ resolveCodexCommand: resolveCodexComman
import {
_internals as realHomeInternals,
awaitRealHomeCodexHookTrust,
ensureRealHomeCodexHookState,
isRealHomeCodexHookLaneUsable
} from './codex-real-home-hook-install'
@@ -182,22 +181,6 @@ describe('a slow codex app-server start', () => {
expect(server.sessions).toBe(1)
})
it('lets a resume into the real home wait until the grant settles', async () => {
const server = installAppServer(15_000)
expect(await launch()).toBe('granting')
let settled = false
const resumed = awaitRealHomeCodexHookTrust().then((lane) => {
settled = true
return lane
})
await new Promise((resolve) => setTimeout(resolve, 50))
expect(settled).toBe(false)
server.start()
expect(await resumed).toBe('installed')
})
it('starts no cooldown after a timeout: the next launch tries again at once', async () => {
const hung = installAppServer(10 * 60_000)
hung.start()
@@ -73,8 +73,7 @@ vi.mock('./main-process-state', async () => {
}
})
const { CODEX_BACKGROUND_TRUST_GRANT_TIMEOUT_MS, _internals: grantInternals } =
await import('../codex/codex-hook-trust-grant')
const { _internals: grantInternals } = await import('../codex/codex-hook-trust-grant')
const { _internals: realHomeInternals } = await import('../codex/codex-real-home-hook-install')
const { getOrcaManagedCodexHomePath } = await import('../codex/codex-home-paths')
const { prepareCodexRuntimeHomeForLaunch } = await import('./codex-launch-preparation')
@@ -201,7 +200,7 @@ describe('a Codex launch while the real-home approval hangs', () => {
})
describe('a Codex resume into the real ~/.codex while its approval runs', () => {
it('spawns once Codex approves the entry, never beside an unapproved one', async () => {
it('starts at once, and the entry is approved when the grant lands', async () => {
let approve: () => void = () => {}
const approval = new Promise<void>((resolve) => {
approve = resolve
@@ -227,27 +226,11 @@ describe('a Codex resume into the real ~/.codex while its approval runs', () =>
}
})
const resumed = resume()
expect(await settlesWithin(resumed, 200)).toBe(false)
expect(await settlesWithin(resume(), 200)).toBe(true)
approve()
await resumed
await realHomeInternals.settledLaneForTesting()
const trust = realHomeOrcaEntryTrust()
expect(trust.length).toBeGreaterThan(0)
expect(trust.every((state) => state === 'trusted')).toBe(true)
})
it('spawns at the approval deadline with the unapproved entries withdrawn', async () => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] })
grantInternals.setGrantSessionRunner(() => new Promise(() => {}))
let spawned = false
const resumed = resume().then(() => {
spawned = true
})
await vi.advanceTimersByTimeAsync(CODEX_BACKGROUND_TRUST_GRANT_TIMEOUT_MS - 1)
expect(spawned).toBe(false)
await vi.advanceTimersByTimeAsync(1)
await resumed
expect(realHomeOrcaEntryTrust()).toEqual([])
})
})
@@ -20,7 +20,6 @@ const mocks = vi.hoisted(() => {
installForLaunchPrep: vi.fn(async () => {}),
refreshRuntimeUserHooksForLaunchPrep: vi.fn(async () => {}),
ensureRealHomeCodexHookState: vi.fn(async () => 'installed' as const),
awaitRealHomeCodexHookTrust: vi.fn(async () => 'installed' as const),
prepareCodexSessionResume: vi.fn()
}
})
@@ -35,7 +34,6 @@ vi.mock('../codex/hook-service', () => ({
}
}))
vi.mock('../codex/codex-real-home-hook-install', () => ({
awaitRealHomeCodexHookTrust: mocks.awaitRealHomeCodexHookTrust,
ensureRealHomeCodexHookState: mocks.ensureRealHomeCodexHookState
}))
// Why: the real predicate, without loading every agent's hook service.
@@ -166,8 +164,6 @@ describe('Codex launch prep honours the per-agent hook opt-out', () => {
expect(mocks.ensureRealHomeCodexHookState).toHaveBeenCalledWith(
expect.objectContaining({ hooksEnabled: codexHooksOn, writePolicy: 'add-missing-only' })
)
// Why: a resume has no managed home to fall back to, so it waits for the grant to settle.
expect(mocks.awaitRealHomeCodexHookTrust).toHaveBeenCalledOnce()
expect(mocks.installForLaunchPrep).not.toHaveBeenCalled()
expect(mocks.refreshRuntimeUserHooksForLaunchPrep).not.toHaveBeenCalled()
}
@@ -6,10 +6,7 @@ import { prepareCodexSessionResume } from '../codex/codex-session-resume-prepara
import { prepareLegacySharedCodexSessionResume } from '../codex/codex-legacy-session-resume'
import { ManagedCodexHomeTemporarilyUnavailableError } from '../codex-accounts/host-codex-managed-home-ownership'
import { codexHookService } from '../codex/hook-service'
import {
awaitRealHomeCodexHookTrust,
ensureRealHomeCodexHookState
} from '../codex/codex-real-home-hook-install'
import { ensureRealHomeCodexHookState } from '../codex/codex-real-home-hook-install'
import { isAgentStatusHooksEnabledForAgent } from '../agent-hooks/managed-agent-hook-controls'
import { markCodexProjectTrusted } from '../agent-trust-presets'
import { awaitAgentTrustWriteWithinDeadline } from '../agent-trust-write-deadline'
@@ -106,9 +103,6 @@ export async function prepareCodexSessionResumeForLaunch(args: {
userDataPath: app.getPath('userData'),
writePolicy: 'add-missing-only'
})
// Why wait: an unapproved entry would show hook review in this pane, and
// the grant's own settle is the only one that cannot race Codex's write.
await awaitRealHomeCodexHookTrust()
} else if (hooksEnabled) {
await codexHookService.installForLaunchPrep(resumeHome)
} else {