fix native Codex runner E2E authentication

This commit is contained in:
Dotta committed 2026-08-27 09:59:20 -05:00
1 parent b9b92fccea
commit fda4d5927d
4 files changed
+39 -5

No files matched your search

@@ -106,6 +106,25 @@ describe("runtime context materialization", () => {
}
});
it("materializes a configured API key into the isolated Codex home", async () => {
const root = await mkdtemp(join(tmpdir(), "paperclip-runtime-context-api-key-"));
roots.push(root);
const sourceCodexHome = join(root, "source-codex-home");
const codexHome = join(root, "codex-home");
await mkdir(sourceCodexHome, { recursive: true });
await writeFile(join(sourceCodexHome, "auth.json"), JSON.stringify({ OPENAI_API_KEY: "host-key-must-not-win" }), { mode: 0o600 });
await prepareIsolatedCodexHome({
context: null,
codexHome,
sourceCodexHome,
apiKey: "configured-agent-key",
});
await expect(readFile(join(codexHome, "auth.json"), "utf8")).resolves.toBe(JSON.stringify({ OPENAI_API_KEY: "configured-agent-key" }));
expect((await stat(join(codexHome, "auth.json"))).mode & 0o777).toBe(0o600);
});
it("reconciles changed and empty assignments without following unexpected symlinks", async () => {
const root = await mkdtemp(join(tmpdir(), "paperclip-runtime-context-reconcile-"));
roots.push(root);
@@ -51,7 +51,7 @@ export async function materializeNativeRuntimeSkills(context: NativeRuntimeConte
}
}
export async function prepareIsolatedCodexHome(input: { context: NativeRuntimeContextSnapshot | null; codexHome: string; sourceCodexHome?: string | null; nativeMcp?: NativeMcpLaunchBinding | null }): Promise<void> {
export async function prepareIsolatedCodexHome(input: { context: NativeRuntimeContextSnapshot | null; codexHome: string; sourceCodexHome?: string | null; nativeMcp?: NativeMcpLaunchBinding | null; apiKey?: string | null }): Promise<void> {
await mkdir(input.codexHome, { recursive: true, mode: 0o700 });
await chmod(input.codexHome, 0o700);
await materializeNativeRuntimeSkills(input.context, join(input.codexHome, "skills"));
@@ -64,6 +64,19 @@ export async function prepareIsolatedCodexHome(input: { context: NativeRuntimeCo
"",
].join("\n"), { mode: 0o600 });
}
const apiKey = input.apiKey;
if (apiKey?.trim()) {
// Codex app-server intentionally receives a small allowlisted process
// environment and does not inherit OPENAI_API_KEY. Materialize an
// API-key-only credential in the per-session Codex home instead. That
// home is excluded from durable backup credentials and removed with the
// isolated runner state.
const target = join(input.codexHome, "auth.json");
await rm(target, { force: true });
await writeFile(target, JSON.stringify({ OPENAI_API_KEY: apiKey }), { mode: 0o600 });
await chmod(target, 0o600);
return;
}
const sourceHome = input.sourceCodexHome?.trim();
if (!sourceHome) return;
const sourceAuth = join(sourceHome, "auth.json");
@@ -1376,6 +1376,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
sourceCodexHome:
this.options.sourceCodexHome ??
resolveSourceCodexHome(this.options.environment),
apiKey: this.options.environment?.OPENAI_API_KEY,
nativeMcp: nativeMcpLaunchBinding(this.options.environment),
});
}
@@ -1698,6 +1699,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
sourceCodexHome:
this.options.sourceCodexHome ??
resolveSourceCodexHome(this.options.environment),
apiKey: this.options.environment?.OPENAI_API_KEY,
nativeMcp: nativeMcpLaunchBinding(this.options.environment),
});
}
+4 -4
View File
@@ -253,7 +253,7 @@ function normalizePlanMarkdown(body: string | null | undefined) {
return (body ?? "").replaceAll("\\_", "_");
}
function renderedPlanMarkerPattern(marker: string) {
function renderedMarkerPattern(marker: string) {
return new RegExp(
marker
.split("_")
@@ -496,7 +496,7 @@ for (const execution of executions) {
await expect(
page
.getByTestId("task-chat-plan-preview")
.filter({ hasText: renderedPlanMarkerPattern(planMarkers.draft) })
.filter({ hasText: renderedMarkerPattern(planMarkers.draft) })
.last(),
).toBeVisible({ timeout: 30_000 });
await captureScreenshot(
@@ -580,7 +580,7 @@ for (const execution of executions) {
page
.getByTestId("task-chat-plan-preview")
.filter({
hasText: renderedPlanMarkerPattern(planMarkers.revised),
hasText: renderedMarkerPattern(planMarkers.revised),
})
.last(),
).toBeVisible({ timeout: 30_000 });
@@ -854,7 +854,7 @@ for (const execution of executions) {
page
.getByTestId("task-chat-thread")
.getByTestId("task-chat-agent-bubble")
.filter({ hasText: marker })
.filter({ hasText: renderedMarkerPattern(marker) })
.last(),
).toBeVisible({ timeout: 30_000 });
await expect(