fix(agent-hooks): keep Orca's own CODEX_HOME out of the host probe

Orca exports CODEX_HOME/ORCA_CODEX_HOME for its managed Codex accounts. The probe inherited them, so it reported Orca's own account home as if the host user had redirected there — which broke the Node 18 managed-hook smoke and would have installed hooks into the managed account home on any client that runs the relay in-process. The child now gets the installer's HOME pinned and both variables stripped; a redirect the user's profile or launcher sets is unaffected, since the login shell re-exports it.
This commit is contained in:
Neil
2026-09-11 01:20:32 -07:00
parent 41c388f44a
commit b8bfd89ebb
3 changed files with 70 additions and 3 deletions
@@ -1,8 +1,11 @@
import { afterEach, describe, expect, it } from 'vitest'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { probeCodexHomeViaAppServer } from './codex-app-server-home-probe'
import {
buildCodexProbeEnvironment,
probeCodexHomeViaAppServer
} from './codex-app-server-home-probe'
// The probe speaks to a real child over pipes; `/bin/sh` is the shell it uses
// on every host that reaches this code (Windows remotes never install hooks).
@@ -122,3 +125,46 @@ sleep 30
expect(Date.now() - startedAt).toBeLessThan(5_000)
})
})
describe('buildCodexProbeEnvironment', () => {
afterEach(() => {
vi.unstubAllEnvs()
})
it('drops the CODEX_HOME Orca injected for its own managed accounts', () => {
// Orca exports these for its managed Codex accounts. Reading one back would
// report Orca's own answer as if it were the host user's configuration.
vi.stubEnv('CODEX_HOME', '/orca/codex-accounts/abc/home')
vi.stubEnv('ORCA_CODEX_HOME', '/orca/codex-accounts/abc/home')
const env = buildCodexProbeEnvironment('/home/dev')
expect(env.CODEX_HOME).toBeUndefined()
expect(env.ORCA_CODEX_HOME).toBeUndefined()
})
it('pins HOME to the home the installer resolved', () => {
expect(buildCodexProbeEnvironment('/home/dev').HOME).toBe('/home/dev')
})
onPosix('asks Codex under the installer s home, not Orca s injected one', async () => {
vi.stubEnv('CODEX_HOME', '/orca/codex-accounts/abc/home')
const fake = await withFakeCodex(
`#!/bin/sh
read -r _line
home="\${CODEX_HOME:-$HOME/.codex}"
printf '{"id":1,"result":{"codexHome":"%s"}}\\n' "$home"
sleep 5
`
)
await expect(
probeCodexHomeViaAppServer({
loginShell: '/bin/sh',
loginShellFlag: '-c',
env: { ...buildCodexProbeEnvironment('/home/dev'), PATH: fake.PATH },
timeoutMs: 10_000
})
).resolves.toBe('/home/dev/.codex')
})
})
@@ -20,6 +20,23 @@ const PROBE_TIMEOUT_MS = 12_000
const MAX_STDOUT_BYTES = 64 * 1024
const INITIALIZE_ID = 1
/**
* The environment the probe hands the child.
*
* `HOME` is pinned to the home the installer resolved, so parent and child
* agree on which account is being configured. `CODEX_HOME`/`ORCA_CODEX_HOME`
* are dropped because Orca injects them for its own managed accounts — reading
* one back would report Orca's own answer as if it were the host user's. A
* value the user's profile or launcher wrapper sets is unaffected: the login
* shell re-exports it, which is the whole point of the probe.
*/
export function buildCodexProbeEnvironment(home: string): NodeJS.ProcessEnv {
const env: NodeJS.ProcessEnv = { ...process.env, HOME: home }
delete env.CODEX_HOME
delete env.ORCA_CODEX_HOME
return env
}
/**
* `codex app-server` exits without answering when stdin reaches EOF, so the
* request is written and the pipe is left open until the reply lands.
+5 -1
View File
@@ -3,7 +3,10 @@ import { basename } from 'node:path'
import { homedir, userInfo } from 'node:os'
import { promisify } from 'node:util'
import { installRemoteManagedAgentHooks } from './remote-managed-hook-installers'
import { probeCodexHomeViaAppServer } from './codex-app-server-home-probe'
import {
buildCodexProbeEnvironment,
probeCodexHomeViaAppServer
} from './codex-app-server-home-probe'
import type { AgentHookTarget } from '../../shared/agent-hook-types'
import { createManagedHookLocalFilesystem } from './managed-hook-local-filesystem'
import { withManagedHookInstallLock } from './managed-hook-install-lock'
@@ -109,6 +112,7 @@ export async function resolveRelayRedirectedCodexHome(
const reported = await probe({
loginShell: shell,
loginShellFlag: flag,
env: buildCodexProbeEnvironment(home),
...(signal ? { signal } : {})
})
const codexHome = reported === null ? null : normalizePosixAgentHome(reported.trim())