From 4e1acc99a3dfd22fc724b788f0beba7ab12e7b26 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:31:05 -0700 Subject: [PATCH] fix(codex): write folder trust into the managed-account CODEX_HOME the launch uses (#33377) Codex workspace trust is also written to the launch CODEX_HOME (managed-account home) at spawn, and native Codex chats pick their launch home before writing trust, so managed-account Codex launches no longer stall on "Trust this folder?". Fixes #27235. --- src/main/agent-trust-presets.test.ts | 16 ++++++++++++++++ src/main/agent-workspace-trust-spawn.ts | 7 +++++-- src/main/agent-workspace-trust.test.ts | 11 +++++++++-- src/main/agent-workspace-trust.ts | 4 +++- src/main/codex/codex-home-paths.ts | 10 ++++++++-- ...spawn-options-agent-workspace-trust.test.ts | 17 ++++++++++++++--- src/main/ipc/pty/spawn-options-assembly.ts | 1 + ...uctured-agent-session-create-intent.test.ts | 10 ++++------ .../structured-agent-runtime-metadata.ts | 18 ++++++++++-------- 9 files changed, 70 insertions(+), 24 deletions(-) diff --git a/src/main/agent-trust-presets.test.ts b/src/main/agent-trust-presets.test.ts index 3e7bc0f81f9..ca5721c4598 100644 --- a/src/main/agent-trust-presets.test.ts +++ b/src/main/agent-trust-presets.test.ts @@ -422,6 +422,22 @@ describe('markCodexProjectTrusted', () => { } }) + it('writes the managed-account CODEX_HOME the launch runs on (#27235)', async () => { + const workspace = mkdtempSync(join(tmpdir(), 'orca-codex-ws-')) + const accountHome = join(testState.userDataDir, 'codex-accounts', 'acct-1', 'home') + try { + await markCodexProjectTrusted( + workspace, + getLocalCodexTrustConfigFiles(testState.fakeHomeDir, accountHome) + ) + expect(readFileSync(join(accountHome, 'config.toml'), 'utf-8')).toContain( + `[projects."${escapeTomlBasicString(realpathSync.native(workspace))}"]\ntrust_level = "trusted"` + ) + } finally { + rmSync(workspace, { recursive: true, force: true }) + } + }) + it('preserves existing config keys and updates an existing project block', async () => { const workspace = mkdtempSync(join(tmpdir(), 'orca-codex-ws-')) const realpath = realpathSync.native(workspace) diff --git a/src/main/agent-workspace-trust-spawn.ts b/src/main/agent-workspace-trust-spawn.ts index 51c2754702c..cf8b6dfee53 100644 --- a/src/main/agent-workspace-trust-spawn.ts +++ b/src/main/agent-workspace-trust-spawn.ts @@ -54,7 +54,8 @@ export function applyAgentWorkspaceTrustToSpawn( env: args.env, claudeAuth: args.claudeAuth, wslDistro: args.wslDistro, - connectionId: args.connectionId + connectionId: args.connectionId, + codexHome: args.codexHome }).then((fields) => { if (fields.agentWorkspaceTrust) { args.spawnOptions.agentWorkspaceTrust = fields.agentWorkspaceTrust @@ -66,6 +67,7 @@ export function applyAgentWorkspaceTrustToSpawn( export async function applyStructuredCodexWorkspaceTrust(args: { workspacePath: string launchEnv: Record + codexHome: string settings: AgentWorkspaceTrustSetting }): Promise { if (!isAgentWorkspaceTrustOn(args.settings)) { @@ -75,6 +77,7 @@ export async function applyStructuredCodexWorkspaceTrust(args: { env: args.launchEnv, claudeAuth: null, wslDistro: null, - connectionId: null + connectionId: null, + codexHome: args.codexHome }) } diff --git a/src/main/agent-workspace-trust.test.ts b/src/main/agent-workspace-trust.test.ts index a1ec37f6296..ddda0ca8c35 100644 --- a/src/main/agent-workspace-trust.test.ts +++ b/src/main/agent-workspace-trust.test.ts @@ -87,11 +87,18 @@ describe('applyAgentWorkspaceTrust on this machine', () => { }) it('writes per-user trust under the home the launch env names, where the agent reads it', async () => { - const context = { ...local, env: { HOME: '/home/agent', USERPROFILE: '/home/agent' } } + const context = { + ...local, + env: { HOME: '/home/agent', USERPROFILE: '/home/agent' }, + codexHome: '/orca/codex-accounts/acct-1/home' + } for (const preset of ['codex', 'cursor', 'copilot', 'qoder', 'antigravity'] as const) { await applyAgentWorkspaceTrust(preset, WORKSPACE, context) } - expect(mocks.codexConfigFiles).toHaveBeenCalledWith('/home/agent') + expect(mocks.codexConfigFiles).toHaveBeenCalledWith( + '/home/agent', + '/orca/codex-accounts/acct-1/home' + ) for (const writer of [mocks.cursor, mocks.copilot, mocks.qoder, mocks.antigravity]) { expect(writer).toHaveBeenCalledWith(WORKSPACE, '/home/agent') } diff --git a/src/main/agent-workspace-trust.ts b/src/main/agent-workspace-trust.ts index 6a80a6d1ea6..e9de9db5a63 100644 --- a/src/main/agent-workspace-trust.ts +++ b/src/main/agent-workspace-trust.ts @@ -20,6 +20,8 @@ export type AgentTrustLaunchContext = { wslDistro: string | null /** SSH connection that runs the agent; null means this machine. */ connectionId: string | null + /** CODEX_HOME Orca injects after this step; the env does not carry it yet. */ + codexHome?: string | null } export type AgentTrustSpawnFields = { @@ -69,7 +71,7 @@ export async function applyAgentWorkspaceTrust( claudeAuth: context.claudeAuth, wslDistro: context.wslDistro }), - codexConfigFiles: () => getLocalCodexTrustConfigFiles(agentHome), + codexConfigFiles: () => getLocalCodexTrustConfigFiles(agentHome, context.codexHome), // Codex shares its config lock queue with hook installs. deadlineMs: preset === 'codex' ? AGENT_TRUST_WRITE_DEADLINE_MS : SHORT_AGENT_TRUST_WRITE_DEADLINE_MS diff --git a/src/main/codex/codex-home-paths.ts b/src/main/codex/codex-home-paths.ts index 2674d25ff7f..4ce3e0421ef 100644 --- a/src/main/codex/codex-home-paths.ts +++ b/src/main/codex/codex-home-paths.ts @@ -48,11 +48,17 @@ export function getOrcaManagedCodexHomePath(): string { } /** Config files an Orca-launched local Codex reads trust from, in the hook installer's lock order. */ -export function getLocalCodexTrustConfigFiles(agentHome: string): string[] { - return [ +export function getLocalCodexTrustConfigFiles( + agentHome: string, + launchCodexHome?: string | null +): string[] { + const files = [ join(getOrcaManagedCodexHomePath(), 'config.toml'), + // Managed accounts launch on their own CODEX_HOME, the only config that launch reads (#27235). + ...(launchCodexHome ? [join(launchCodexHome, 'config.toml')] : []), join(agentHome, '.codex', 'config.toml') ] + return [...new Set(files)] } export function getCodexSessionBackfillStateDirPath(): string { diff --git a/src/main/ipc/pty/spawn-options-agent-workspace-trust.test.ts b/src/main/ipc/pty/spawn-options-agent-workspace-trust.test.ts index 5e40cd7221d..9059f0a8bc6 100644 --- a/src/main/ipc/pty/spawn-options-agent-workspace-trust.test.ts +++ b/src/main/ipc/pty/spawn-options-agent-workspace-trust.test.ts @@ -25,6 +25,7 @@ type BuildInput = { restored?: boolean claudeAuth?: ClaudeRuntimeAuthPreparation | null wslDistro?: string | null + selectedCodexHomePath?: string | null } const RESTORED_PANE: AdoptStablePaneResult = { @@ -67,7 +68,13 @@ function makeDeps(): PtySpawnIpcDeps & PtyRuntimeControllerDeps { function seed( ctx: Pick< PtyIpcSpawnState, - 'env' | 'cwd' | 'launchCommand' | 'claudeAuth' | 'expectedWslDistro' | 'preAdoptedStablePane' + | 'env' + | 'cwd' + | 'launchCommand' + | 'claudeAuth' + | 'expectedWslDistro' + | 'preAdoptedStablePane' + | 'selectedCodexHomePath' >, input: BuildInput ): void { @@ -77,6 +84,7 @@ function seed( ctx.claudeAuth = input.claudeAuth ?? null ctx.expectedWslDistro = input.wslDistro ?? null ctx.preAdoptedStablePane = input.restored ? RESTORED_PANE : null + ctx.selectedCodexHomePath = input.selectedCodexHomePath ?? null } async function build(route: 'renderer' | 'runtime', input: BuildInput, deps = makeDeps()) { @@ -134,13 +142,16 @@ describe.each(['renderer', 'runtime'] as const)('%s spawn builder agent trust', await build(route, { launchAgent: 'codex', command: 'codex', - wslDistro: 'Ubuntu' + wslDistro: 'Ubuntu', + selectedCodexHomePath: '/orca/codex-accounts/acct-1/home' }) expect(applyAgentWorkspaceTrust).toHaveBeenCalledWith('codex', '/repo/wt', { env: expect.objectContaining({ CLAUDE_CONFIG_DIR: '/cfg' }), claudeAuth: null, wslDistro: 'Ubuntu', - connectionId: null + connectionId: null, + // The env lacks CODEX_HOME until the provider injects it (#27235). + codexHome: '/orca/codex-accounts/acct-1/home' }) }) diff --git a/src/main/ipc/pty/spawn-options-assembly.ts b/src/main/ipc/pty/spawn-options-assembly.ts index ffb04ae8e91..4b32d3e585a 100644 --- a/src/main/ipc/pty/spawn-options-assembly.ts +++ b/src/main/ipc/pty/spawn-options-assembly.ts @@ -189,6 +189,7 @@ export async function assemblePtySpawnLaunchOptions(input: PtySpawnLaunchOptions claudeAuth: input.claudeAuth, wslDistro: input.expectedWslDistro, connectionId: connectionId ?? null, + codexHome: input.selectedCodexHomePath, spawnOptions }) if (trustWrite) { diff --git a/src/main/runtime/orca-runtime-structured-agent-session-create-intent.test.ts b/src/main/runtime/orca-runtime-structured-agent-session-create-intent.test.ts index 04af0407e64..46a4079e5a2 100644 --- a/src/main/runtime/orca-runtime-structured-agent-session-create-intent.test.ts +++ b/src/main/runtime/orca-runtime-structured-agent-session-create-intent.test.ts @@ -75,8 +75,8 @@ describe('structured Codex folder trust', () => { expect(attachFingerprintFields(intent)).not.toHaveProperty('hostLaunchDirectory') }) - it('pre-trusts the chat folder before launch preparation, as a Codex terminal launch does', async () => { - const { prepareCodexStructuredLaunch, createIntent } = createCodexIntentRuntime({}) + it('pre-trusts the chat folder in the account home launch preparation selected', async () => { + const { createIntent } = createCodexIntentRuntime({}) await createIntent() @@ -84,11 +84,9 @@ describe('structured Codex folder trust', () => { env: expect.any(Object), claudeAuth: null, wslDistro: null, - connectionId: null + connectionId: null, + codexHome: '/accounts/selected/home' }) - expect(applyAgentWorkspaceTrust.mock.invocationCallOrder[0]).toBeLessThan( - prepareCodexStructuredLaunch.mock.invocationCallOrder[0] - ) }) it('writes nothing with the setting off, and still prepares the launch', async () => { diff --git a/src/main/runtime/structured-agent-runtime-metadata.ts b/src/main/runtime/structured-agent-runtime-metadata.ts index cb06843ad79..6104d224aa7 100644 --- a/src/main/runtime/structured-agent-runtime-metadata.ts +++ b/src/main/runtime/structured-agent-runtime-metadata.ts @@ -91,18 +91,20 @@ async function resolveCodexAccountHomePath( services: StructuredAgentAccountHomeServices ): Promise { const { launchEnv, purpose, workspacePath } = request - if (purpose === 'launch' && workspacePath) { - await applyStructuredCodexWorkspaceTrust({ - workspacePath: await workspacePath(), - launchEnv, - settings: services.workspaceTrustSettings() - }) - } - return resolveStructuredCodexAccountHomePath({ + const codexHome = await resolveStructuredCodexAccountHomePath({ launchEnv, resolveLaunchHome: purpose === 'launch' ? services.prepareCodexLaunchHome : services.readCodexLaunchHome }) + if (purpose === 'launch' && workspacePath) { + await applyStructuredCodexWorkspaceTrust({ + workspacePath: await workspacePath(), + launchEnv, + codexHome, + settings: services.workspaceTrustSettings() + }) + } + return codexHome } // One roster supplies both metadata readers and the host's synchronous adapter factories.