mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user