diff --git a/doc/plans/2026-10-06-default-agent-runner.md b/doc/plans/2026-10-06-default-agent-runner.md index 199012a25a..a4e67d93a1 100644 --- a/doc/plans/2026-10-06-default-agent-runner.md +++ b/doc/plans/2026-10-06-default-agent-runner.md @@ -78,7 +78,26 @@ These corrections update server setup/execution and guide source. The qualified `a200495` package startup and historical task evidence retain their literal component boundaries; no new managed live proof or paid turn is claimed. -Next action: require fresh CI/review on the corrected integrated candidate, refresh the +Fresh review at `e79254b` verifies the preceding fixes and reports one valid +development override edge. The common remote resolver now preserves +`PAPERCLIP_RUNNER_BINARY` only when the measured target OS/CPU matches the +controller. Explicit remote overrides retain priority; every selected artifact +still requires a matching native header. Cross-target selection ignores the +controller override. Seven new baseline failures reproduce the defect; 133 +target/setup/backend/retained/version controls and server typecheck pass after +the correction. Receipt: +`/private/tmp/codex-default-same-host-override-fix-receipt.json`. + +The `e79254b` preview restart preserves all four configurations and both +historical native runs. Its actual served guide now reports the shared GNU floor +and qualified Mac/ARM targets. No native build, Docker build, or provider call +runs locally. Main CI's selector remains unallocated; other PRs share this queue. +No environment approval, owned concurrency conflict, or routing failure was +found. Org capacity versus GitHub allocation is unconfirmed. Receipt: +`/private/tmp/pr2-e792-selector-queue-investigation.json`. Required final CI +aggregates and fresh review remain pending, not passed. + +Next action: require fresh CI/review on the final corrected candidate, refresh the matched preview, and publish final conclusions in the canonical PR's evidence index. Human code-owner approval, managed staging onboarding, and actual post-merge Cloud composition remain outstanding. No merge or production diff --git a/server/src/services/native-runtime/native-codex-runner.test.ts b/server/src/services/native-runtime/native-codex-runner.test.ts index 2648d68b9e..f5341c8a73 100644 --- a/server/src/services/native-runtime/native-codex-runner.test.ts +++ b/server/src/services/native-runtime/native-codex-runner.test.ts @@ -12,7 +12,7 @@ import { resolveRemotePaperclipRunnerBinary, } from "./native-codex-runner.js"; -afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); }); +afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); vi.unstubAllEnvs(); }); describe("installed runner selection", () => { it("uses the runner's platform-aware packaged selection", async () => { @@ -79,6 +79,9 @@ describe("selected remote daemon target", () => { try { vi.stubGlobal("process", { ...process, platform: "darwin", arch: controllerArch }); await writeFile(binary, header(`linux-${targetArch}`)); + const controllerBinary = join(root, "controller-runnerd"); + await writeFile(controllerBinary, header(`darwin-${controllerArch}`), { mode: 0o700 }); + vi.stubEnv("PAPERCLIP_RUNNER_BINARY", controllerBinary); const select = vi.spyOn(runner, "defaultCapabilityRunnerdBinary").mockReturnValue(binary); const target = remote(`Linux\n${targetArch === "arm64" ? "aarch64" : "x86_64"}\n`); expect(await resolveRemotePaperclipRunnerBinary({ runner: target, remoteCwd: "/sandbox-workspace" })).toBe(binary); @@ -87,10 +90,54 @@ describe("selected remote daemon target", () => { } finally { await rm(root, { recursive: true, force: true }); } }, ); + it.each(["linux", "darwin"].flatMap(platform => ["arm64", "x64"].map(architecture => ({ platform, architecture }))))( + "prefers the supervisor's fresh $platform/$architecture override for the same-host target", async ({ platform, architecture }) => { + const root = await mkdtemp(join(tmpdir(), "paperclip-same-host-daemon-")); + try { + vi.stubGlobal("process", { ...process, platform, arch: architecture }); + const fresh = join(root, "fresh-runnerd"), stale = join(root, "stale-packaged-runnerd"); + await writeFile(fresh, Buffer.concat([header(`${platform}-${architecture}`), Buffer.from("fresh")]), { mode: 0o700 }); + await writeFile(stale, Buffer.concat([header(`${platform}-${architecture}`), Buffer.from("stale")]), { mode: 0o700 }); + vi.stubEnv("PAPERCLIP_RUNNER_BINARY", fresh); + const select = vi.spyOn(runner, "defaultCapabilityRunnerdBinary").mockReturnValue(stale); + const target = remote(`${platform === "linux" ? "Linux" : "Darwin"}\n${architecture === "arm64" ? "arm64" : "x86_64"}\n`); + expect(await resolveRemotePaperclipRunnerBinary({ runner: target, remoteCwd: "/workspace" })).toBe(fresh); + expect(select).not.toHaveBeenCalled(); + } finally { await rm(root, { recursive: true, force: true }); } + }, + ); + it.each(["missing", "relative", "malformed", "wrong-architecture"])("retains same-host override policy and target validation (%s)", async scenario => { + const root = await mkdtemp(join(tmpdir(), "paperclip-same-host-daemon-")); + try { + vi.stubGlobal("process", { ...process, platform: "linux", arch: "x64" }); + const override = join(root, "configured-runnerd"), fallback = join(root, "packaged-runnerd"); + if (scenario !== "missing") await writeFile(override, scenario === "malformed" ? Buffer.alloc(64) : header("linux-arm64"), { mode: 0o700 }); + await writeFile(fallback, header("linux-x64"), { mode: 0o700 }); + vi.stubEnv("PAPERCLIP_RUNNER_BINARY", scenario === "relative" ? "relative-runnerd" : override); + vi.spyOn(runner, "defaultCapabilityRunnerdBinary").mockReturnValue(fallback); + const request = resolveRemotePaperclipRunnerBinary({ runner: remote("Linux\nx86_64\n"), remoteCwd: "/workspace" }); + if (scenario === "missing") expect(await request).toBe(fallback); + else await expect(request).rejects.toThrow(scenario === "relative" ? "PAPERCLIP_RUNNER_BINARY must be an absolute path" : "runner_remote_artifact_platform_mismatch"); + } finally { await rm(root, { recursive: true, force: true }); } + }); + it("ignores a relative controller override when the actual target differs", async () => { + const root = await mkdtemp(join(tmpdir(), "paperclip-cross-host-daemon-")); + try { + vi.stubGlobal("process", { ...process, platform: "darwin", arch: "arm64" }); + vi.stubEnv("PAPERCLIP_RUNNER_BINARY", "relative-controller-runnerd"); + const binary = join(root, "linux-runnerd"); + await writeFile(binary, header("linux-arm64")); + const select = vi.spyOn(runner, "defaultCapabilityRunnerdBinary").mockReturnValue(binary); + expect(await resolveRemotePaperclipRunnerBinary({ runner: remote("Linux\naarch64\n"), remoteCwd: "/workspace" })).toBe(binary); + expect(select).toHaveBeenCalledExactlyOnceWith("linux", "arm64"); + } finally { await rm(root, { recursive: true, force: true }); } + }); it.each(["matching", "wrong-architecture", "malformed", "missing"])("verifies an explicit controller-owned remote artifact (%s)", async scenario => { const root = await mkdtemp(join(tmpdir(), "paperclip-ssh-daemon-")); const binary = join(root, "configured-runnerd"); try { + vi.stubGlobal("process", { ...process, platform: "linux", arch: "arm64" }); + vi.stubEnv("PAPERCLIP_RUNNER_BINARY", "invalid-relative-controller-override"); if (scenario !== "missing") await writeFile(binary, scenario === "malformed" ? Buffer.alloc(64) : header(scenario === "matching" ? "linux-arm64" : "linux-x64")); const select = vi.spyOn(runner, "defaultCapabilityRunnerdBinary"); const request = resolveRemotePaperclipRunnerBinary({ runner: remote("Linux\naarch64\n"), remoteCwd: "/workspace", configuredPath: binary }); diff --git a/server/src/services/native-runtime/native-codex-runner.ts b/server/src/services/native-runtime/native-codex-runner.ts index 962b7fb30e..c5cda8a994 100644 --- a/server/src/services/native-runtime/native-codex-runner.ts +++ b/server/src/services/native-runtime/native-codex-runner.ts @@ -224,7 +224,13 @@ export async function resolveRemotePaperclipRunnerBinary(input: { } const configured = input.configuredPath?.trim(); if (configured && !isAbsolute(configured)) throw new Error("PAPERCLIP_RUNNER_REMOTE_BINARY_PATH must be an absolute path"); - const binary = configured || defaultCapabilityRunnerdBinary(platform, architecture); + // A same-host sandbox can use the supervisor's fresh development artifact. + // Cross-platform targets must never inherit the controller's override. + const controllerOverride = platform === process.platform && architecture === process.arch + ? process.env.PAPERCLIP_RUNNER_BINARY : undefined; + const binary = configured || (controllerOverride + ? resolvePaperclipRunnerBinary(controllerOverride) + : defaultCapabilityRunnerdBinary(platform, architecture)); let bytes: Buffer; try { bytes = readFileSync(binary); } catch (cause) { throw new Error(`runner_remote_artifact_unavailable: install the Paperclip release containing the ${platform}-${architecture} daemon or configure PAPERCLIP_RUNNER_REMOTE_BINARY_PATH`, { cause }); }