From c2e517d948ad2ad63424240b79634eba8d6be892 Mon Sep 17 00:00:00 2001 From: nickyleach <331803+nickyleach@users.noreply.github.com> Date: Fri, 11 Sep 2026 21:34:52 +0000 Subject: [PATCH] fix(acpx-engine): check the final env for Codex host-key auth selection The Codex api-key auth-request check ran on the run's explicit env, before the launch merged the projected host environment in. A local Codex launch could inherit OPENAI_API_KEY or CODEX_API_KEY from the host after that check, so the launched process had the credential but never got the required api-key authentication request. Move the check into resolveRuntimeEnv, after the host projection and explicit env merge produce the final environment. This covers every launch path (local, runner-less fallback, remote sandbox), because they all resolve their launch env through this one function. Co-authored-by: Paperclip --- .../src/acpx-engine/execute.test.ts | 29 +++++++++++++++++++ .../adapter-utils/src/acpx-engine/execute.ts | 27 +++++++++-------- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/packages/adapter-utils/src/acpx-engine/execute.test.ts b/packages/adapter-utils/src/acpx-engine/execute.test.ts index 1e3b8abd00..b836b87df6 100644 --- a/packages/adapter-utils/src/acpx-engine/execute.test.ts +++ b/packages/adapter-utils/src/acpx-engine/execute.test.ts @@ -1572,6 +1572,35 @@ describe("shared ACPX engine runtime behavior", () => { }, ); + it.each(["OPENAI_API_KEY", "CODEX_API_KEY"] as const)( + "selects Codex ACP API-key authentication when only the host process provides %s", + async (apiKeyName) => { + const root = await makeTempRoot(); + const codexHome = path.join(root, "codex-home"); + await fs.mkdir(codexHome, { recursive: true }); + + // Simulate a local launch that inherits a provider key from the host + // process environment. No adapter config sets the key directly, so the + // launched env only receives it through host projection. + vi.stubEnv(apiKeyName, "sk-host-inherited-key"); + try { + const { sessionInputs } = await runExecutor({ + agent: "codex", + stateDir: path.join(root, "state"), + env: { CODEX_HOME: codexHome }, + paperclipRuntimeSkills: [], + paperclipSkillSync: { desiredSkills: [] }, + }); + + const env = (sessionInputs[0]!.sessionOptions as { env: Record }).env; + expect(env[apiKeyName]).toBe("sk-host-inherited-key"); + expect(env.DEFAULT_AUTH_REQUEST).toBe(JSON.stringify({ methodId: "api-key" })); + } finally { + vi.unstubAllEnvs(); + } + }, + ); + it("busts the session fingerprint when resolved adapter env changes but not across wakes", async () => { const root = await makeTempRoot(); const stateDir = path.join(root, "state"); diff --git a/packages/adapter-utils/src/acpx-engine/execute.ts b/packages/adapter-utils/src/acpx-engine/execute.ts index b4971bd718..a53ca0ddc0 100644 --- a/packages/adapter-utils/src/acpx-engine/execute.ts +++ b/packages/adapter-utils/src/acpx-engine/execute.ts @@ -1967,17 +1967,6 @@ async function buildRuntime(input: { // are absent from tempKeysApplied and keep their compatibility protection. if (!scratchKeys.has(key) || value !== scratch.dir) resolvedAdapterEnv[key] = value; } - // codex-acp supports both key names, but ACP clients must select its - // api-key authentication method during session creation. Without this - // request, the server advertises authentication and rejects session/new even - // though the credential is present in the launched process environment. - if ( - acpxAgent === "codex" && - (env.OPENAI_API_KEY || env.CODEX_API_KEY) && - !env.DEFAULT_AUTH_REQUEST - ) { - env.DEFAULT_AUTH_REQUEST = JSON.stringify({ methodId: "api-key" }); - } if (authToken) env.PAPERCLIP_API_KEY = authToken; // For the claude agent, set model via ANTHROPIC_MODEL at startup rather than // via session/set_config_option — the ACP server's set_config_option handler @@ -2629,11 +2618,25 @@ function resolveRuntimeEnv( env, (options.platform ?? process.platform) === "win32", ); - return Object.fromEntries( + const finalEnv = Object.fromEntries( Object.entries(mergedEnv).filter( (entry): entry is [string, string] => typeof entry[1] === "string", ), ); + // codex-acp supports both key names, but ACP clients must select its + // api-key authentication method during session creation. Without this + // request, the server advertises authentication and rejects session/new even + // though the credential is present in the launched process environment. Check + // the final merged environment, not just the explicit run config, so a host + // key the local launch inherits still selects this default. + if ( + acpxAgent === "codex" && + (finalEnv.OPENAI_API_KEY || finalEnv.CODEX_API_KEY) && + !finalEnv.DEFAULT_AUTH_REQUEST + ) { + finalEnv.DEFAULT_AUTH_REQUEST = JSON.stringify({ methodId: "api-key" }); + } + return finalEnv; } function mergeRuntimeEnvironment(