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.
This commit is contained in:
Neil
2026-10-24 20:31:05 -07:00
committed by GitHub
parent f392c6bd46
commit 4e1acc99a3
9 changed files with 70 additions and 24 deletions
+16
View File
@@ -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)
+5 -2
View File
@@ -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<string, string | undefined>
codexHome: string
settings: AgentWorkspaceTrustSetting
}): Promise<void> {
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
})
}
+9 -2
View File
@@ -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')
}
+3 -1
View File
@@ -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
+8 -2
View File
@@ -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 {
@@ -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'
})
})
@@ -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) {
@@ -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 () => {
@@ -91,18 +91,20 @@ async function resolveCodexAccountHomePath(
services: StructuredAgentAccountHomeServices
): Promise<string> {
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.