From 9708571b43c0fc342cc2c89bae3fd8ee4534d56b Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 12 Oct 2026 17:10:28 -0500 Subject: [PATCH 1/2] test(claude): retain experimental harness-switch policy coverage Co-Authored-By: Paperclip --- server/src/__tests__/agent-adapter-validation-routes.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/__tests__/agent-adapter-validation-routes.test.ts b/server/src/__tests__/agent-adapter-validation-routes.test.ts index 51fdc63bc6b..39f77402b9e 100644 --- a/server/src/__tests__/agent-adapter-validation-routes.test.ts +++ b/server/src/__tests__/agent-adapter-validation-routes.test.ts @@ -711,7 +711,7 @@ describe("agent routes adapter validation", () => { expect(mockAgentService.create).not.toHaveBeenCalled(); }); - it("rechecks selection policy when a saved native adapter switches its underlying harness", async () => { + it("rechecks selection policy when a saved native adapter switches to an experimentally gated harness", async () => { mockAgentService.getById.mockResolvedValue({ ...(await mockAgentService.getById()), adapterType: "paperclip_runner", adapterConfig: { provider: "codex" } }); mockInstanceSettingsService.getExperimental.mockResolvedValue({ enableNativeRunner: false }); const probe = vi.fn(); @@ -721,7 +721,7 @@ describe("agent routes adapter validation", () => { const app = await createApp(); const res = await requestApp(app, baseUrl => request(baseUrl) .post("/api/companies/company-1/adapters/paperclip_runner/test-environment") - .send({ agentId: "11111111-1111-4111-8111-111111111111", adapterConfig: { provider: "acpx", acpxAgent: "claude" } })); + .send({ agentId: "11111111-1111-4111-8111-111111111111", adapterConfig: { provider: "acpx", acpxAgent: "pi" } })); expect(res.status, JSON.stringify(res.body)).toBe(422); expect(res.body.details).toMatchObject({ code: "paperclip_runner_rollout_disabled" }); expect(probe).not.toHaveBeenCalled(); From 804a38a4e1f89802909d70838b696bb8c46a4e34 Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 12 Oct 2026 17:15:13 -0500 Subject: [PATCH 2/2] test(claude): bind skills fixtures to explicit runner behavior Co-Authored-By: Paperclip --- doc/plans/2026-10-12-claude-runner-default.md | 18 ++++++++++ .../codex-local/src/ui/build-config.test.ts | 4 ++- .../src/__tests__/agent-skills-routes.test.ts | 35 +++++++++++++++---- .../codex-local/config-fields.test.tsx | 6 ---- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/doc/plans/2026-10-12-claude-runner-default.md b/doc/plans/2026-10-12-claude-runner-default.md index 929a337c06d..6f135c93bb5 100644 --- a/doc/plans/2026-10-12-claude-runner-default.md +++ b/doc/plans/2026-10-12-claude-runner-default.md @@ -399,3 +399,21 @@ port has a listener but current API health did not respond, so its current polic state is unconfirmed rather than asserted stopped or reverified. No accounting hold was cleared by this audit. Timer heartbeats and automatic paid retries remain disabled. No local Docker or Rust builds occurred. + + +## Complete CI fixture corrections — 2026-10-12 + +The first current-head matrix exposed stale fixture assumptions after graduation: +experimental harness-switch rejection still targeted Claude, Linux SSH creation +still expected Legacy, and Legacy instruction/skill cases relied on an implicit +runner. The gating case now uses Pi; all 56 validation-route controls pass. +Qualified SSH creation asserts native Claude/Opus 5.5; all 92 permission-route +controls pass. Legacy instruction/skill fixtures explicitly choose Legacy. +The native Claude creation/hire matrix now checks preserved skill-version pins +and CEO defaults, while omitting the legacy operational skill supplied by PRP. +All 111 skills-route controls pass. Builder/config controls retain the shared +package and mounted-form evidence rather than a duplicate UI pure-function test +that expected blank effort to restore saved values. The historical missing-form +fixture now deletes the property instead of assigning an invalid typed undefined; +Codex package noemit passes. These corrections make no provider calls and change +no runtime artifact identities. diff --git a/packages/adapters/codex-local/src/ui/build-config.test.ts b/packages/adapters/codex-local/src/ui/build-config.test.ts index a160fc6ef27..12df8af81d4 100644 --- a/packages/adapters/codex-local/src/ui/build-config.test.ts +++ b/packages/adapters/codex-local/src/ui/build-config.test.ts @@ -121,7 +121,9 @@ describe("buildPaperclipRunnerConfig", () => { it.each(["codex", "claude"])("preserves, overrides and explicitly clears saved %s reasoning effort", harness => { const profile = harness === "codex" ? { provider: "codex" } : { provider: "acpx", acpxAgent: "claude" }; const values = makeValues({ adapterSchemaValues: { ...profile, modelReasoningEffort: "high", reasoningEffort: "low", effort: "low" } }); - expect(buildPaperclipRunnerConfig({ ...values, thinkingEffort: undefined }).modelReasoningEffort).toBe("high"); + const unhydrated = { ...values }; + Reflect.deleteProperty(unhydrated, "thinkingEffort"); + expect(buildPaperclipRunnerConfig(unhydrated).modelReasoningEffort).toBe("high"); expect(buildPaperclipRunnerConfig({ ...values, thinkingEffort: "max" }).modelReasoningEffort).toBe("max"); const cleared = buildPaperclipRunnerConfig({ ...values, thinkingEffort: "" }); for (const key of ["modelReasoningEffort", "reasoningEffort", "effort"]) expect(cleared).not.toHaveProperty(key); diff --git a/server/src/__tests__/agent-skills-routes.test.ts b/server/src/__tests__/agent-skills-routes.test.ts index 1cfbbe3e936..8ccb273b2f9 100644 --- a/server/src/__tests__/agent-skills-routes.test.ts +++ b/server/src/__tests__/agent-skills-routes.test.ts @@ -1005,6 +1005,7 @@ describe("agent skill routes", () => { name: "QA Agent", role: "engineer", adapterType: "claude_local", + runner: "legacy", desiredSkills: ["paperclip"], adapterConfig: {}, })); @@ -1038,6 +1039,7 @@ describe("agent skill routes", () => { name: "QA Agent", role: "engineer", adapterType: "claude_local", + runner: "legacy", desiredSkills: [{ key: "paperclipai/paperclip/paperclip", versionId: "22222222-2222-4222-8222-222222222222", @@ -1057,6 +1059,7 @@ describe("agent skill routes", () => { name: "Security Engineer", role: "security", adapterType: "claude_local", + runner: "legacy", adapterConfig: {}, })); @@ -1088,6 +1091,7 @@ describe("agent skill routes", () => { name: "QA Agent", role: "engineer", adapterType: "claude_local", + runner: "legacy", adapterConfig: {}, instructionsBundle: { files: { @@ -1124,6 +1128,7 @@ describe("agent skill routes", () => { name: "QA Agent", role: "engineer", adapterType: "claude_local", + runner: "legacy", adapterConfig: { instructionsFilePath: "/tmp/existing/AGENTS.md", promptTemplate: "You are QA.", @@ -1150,6 +1155,7 @@ describe("agent skill routes", () => { name: "CEO", role: "ceo", adapterType, + ...(adapterType === "claude_local" ? { runner: "legacy" } : {}), adapterConfig: adapterType === "paperclip_runner" ? { provider: "codex" } : {}, })); @@ -1180,6 +1186,7 @@ describe("agent skill routes", () => { name: "Research Lead", role: "ceo", adapterType, + ...(adapterType === "claude_local" ? { runner: "legacy" } : {}), adapterConfig: adapterType === "paperclip_runner" ? { provider: "codex" } : {}, instructionsBundle: { entryFile: "AGENTS.md", files: customFiles }, })); @@ -1206,6 +1213,7 @@ describe("agent skill routes", () => { name: "Engineer", role: "engineer", adapterType: "claude_local", + runner: "legacy", adapterConfig: {}, })); @@ -1233,6 +1241,7 @@ describe("agent skill routes", () => { name: "Ada", role: "general", adapterType: "claude_local", + runner: "legacy", adapterConfig: {}, onboardingFirstAgent: true, })); @@ -1264,7 +1273,12 @@ describe("agent skill routes", () => { { adapterType: "codex_local", runner: "legacy", platform: "darwin", expectedType: "codex_local" }, { adapterType: "codex_local", runner: "auto", platform: "win32", expectedType: "codex_local" }, ]; - it.each([...codexRunnerCases, { adapterType: "claude_local", runner: "auto", platform: "linux", expectedType: "claude_local" }].flatMap((runnerCase) => [ + const creationRunnerCases = [...codexRunnerCases, + { adapterType: "claude_local", runner: "auto", platform: "linux", expectedType: "claude_local" }, + { adapterType: "claude_local", runner: "auto", platform: "darwin", expectedType: "paperclip_runner" }, + { adapterType: "claude_local", runner: "legacy", platform: "darwin", expectedType: "claude_local" }, + ]; + it.each(creationRunnerCases.flatMap((runnerCase) => [ ["agents", "paperclipai/paperclip/paperclip-create-agent"], ["agent-hires", "paperclipai/paperclip/paperclip-create-agent"], ["agents", "paperclip"], @@ -1288,7 +1302,7 @@ describe("agent skill routes", () => { expect(input.role).toBe("general"); expect(input.adapterType).toBe(expectedType); const native = expectedType === "paperclip_runner"; - if (native) expect(input.adapterConfig.provider).toBe("codex"); + if (native) expect(input.adapterConfig).toMatchObject(adapterType === "claude_local" ? { provider: "acpx", acpxAgent: "claude", model: "claude-opus-5-5" } : { provider: "codex" }); const canonicalKey = skill === "paperclip" ? "paperclipai/paperclip/paperclip" : skill; const expected = ["paperclip", "paperclip-board", "paperclip-converting-plans-to-tasks", "paperclip-create-agent", "para-memory-files", "first-task"] // Native PRP supplies the operational contract; role skills and their pins remain assigned. @@ -1308,17 +1322,17 @@ describe("agent skill routes", () => { expect(mockAgentService.create.mock.calls[0][1].adapterConfig.paperclipSkillSync).toBeUndefined(); }); - it.each(codexRunnerCases.flatMap((runnerCase) => ["agents", "agent-hires"].map((route) => ({ ...runnerCase, route }))))("assigns core skills without first-task to ordinary CEOs via $route using $runner on $platform", async ({ route, runner, platform, expectedType }) => { + it.each(creationRunnerCases.flatMap((runnerCase) => ["agents", "agent-hires"].map((route) => ({ ...runnerCase, route }))))("assigns core skills without first-task to ordinary CEOs via $route using $runner on $platform", async ({ route, runner, platform, expectedType, adapterType }) => { runnerTarget.platform = platform; runnerTarget.architecture = platform === "darwin" ? "arm64" : "x64"; const res = await request(await createApp()) .post(`/api/companies/company-1/${route}`) - .send({ name: "CEO", role: "ceo", adapterType: "codex_local", ...(runner === "auto" ? {} : { runner }) }); + .send({ name: "CEO", role: "ceo", adapterType, ...(runner === "auto" ? {} : { runner }) }); expect(res.status, JSON.stringify(res.body)).toBe(201); const input = mockAgentService.create.mock.calls[0][1]; expect(input.adapterType).toBe(expectedType); const native = expectedType === "paperclip_runner"; - if (native) expect(input.adapterConfig.provider).toBe("codex"); + if (native) expect(input.adapterConfig).toMatchObject(adapterType === "claude_local" ? { provider: "acpx", acpxAgent: "claude", model: "claude-opus-5-5" } : { provider: "codex" }); const desiredSkills = input.adapterConfig.paperclipSkillSync.desiredSkills; const expected = ["paperclip", "paperclip-board", "paperclip-converting-plans-to-tasks", "paperclip-create-agent", "para-memory-files"] .filter((name) => !native || name !== "paperclip") @@ -1334,7 +1348,8 @@ describe("agent skill routes", () => { type: "agent", agentId: "11111111-1111-4111-8111-111111111111", companyId: "company-1", })) .post("/api/companies/company-1/agent-hires") - .send({ name: "Biff", role: "general", adapterType: "claude_local", onboardingFirstAgent: true }); + .send({ name: "Biff", role: "general", adapterType: "claude_local", + runner: "legacy", onboardingFirstAgent: true }); expect(res.status, JSON.stringify(res.body)).toBe(201); expect(mockAgentService.create.mock.calls[0][1].adapterConfig.paperclipSkillSync).toBeUndefined(); await vi.waitFor(() => expect(mockAgentInstructionsService.materializeManagedBundle).toHaveBeenCalled()); @@ -1371,6 +1386,7 @@ describe("agent skill routes", () => { name: "QA Agent", role: "engineer", adapterType: "claude_local", + runner: "legacy", desiredSkills: ["paperclip"], adapterConfig: {}, }); @@ -1396,6 +1412,7 @@ describe("agent skill routes", () => { name: "First Lead", role: "ceo", adapterType: "claude_local", + runner: "legacy", adapterConfig: {}, }); @@ -1453,6 +1470,7 @@ describe("agent skill routes", () => { name: "First Lead", role: "ceo", adapterType: "claude_local", + runner: "legacy", desiredSkills: ["paperclip"], adapterConfig: {}, }); @@ -1474,6 +1492,7 @@ describe("agent skill routes", () => { name: "QA Agent", role: "engineer", adapterType: "claude_local", + runner: "legacy", adapterConfig: {}, }); @@ -1491,6 +1510,7 @@ describe("agent skill routes", () => { name: "QA Agent", role: "engineer", adapterType: "claude_local", + runner: "legacy", desiredSkills: [{ key: "paperclipai/paperclip/paperclip", versionId: "22222222-2222-4222-8222-222222222222", @@ -1515,6 +1535,7 @@ describe("agent skill routes", () => { role: "engineer", icon: "crown", adapterType: "claude_local", + runner: "legacy", desiredSkills: ["paperclip"], adapterConfig: {}, sourceIssueId, @@ -1569,6 +1590,7 @@ describe("agent skill routes", () => { name: "QA Agent", role: "engineer", adapterType: "claude_local", + runner: "legacy", adapterConfig: {}, instructionsBundle: { files: { @@ -1605,6 +1627,7 @@ describe("agent skill routes", () => { name: "QA Agent", role: "engineer", adapterType: "claude_local", + runner: "legacy", adapterConfig: { instructionsFilePath: "/tmp/existing/AGENTS.md", promptTemplate: "You are QA.", diff --git a/ui/src/adapters/codex-local/config-fields.test.tsx b/ui/src/adapters/codex-local/config-fields.test.tsx index c63d3f725a4..addc2b47b00 100644 --- a/ui/src/adapters/codex-local/config-fields.test.tsx +++ b/ui/src/adapters/codex-local/config-fields.test.tsx @@ -12,7 +12,6 @@ import { CodexLocalConfigFields } from "./config-fields"; import { buildCodexRunnerConfig } from "./config-fields"; import { defaultCreateValues } from "@/components/agent-config-defaults"; import { resolveCodexLocalCliPolicy } from "@paperclipai/adapter-codex-local"; -import { buildPaperclipRunnerConfig } from "@paperclipai/adapter-codex-local/ui"; beforeAll(() => { Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); }); @@ -55,11 +54,6 @@ async function renderRunner(config: Record, expand?: string, in } describe("Paperclip Runner Codex configuration", () => { - it.each(["", "max"])("preserves or overrides saved native Codex reasoning effort (%s)", thinkingEffort => { - const values = { ...defaultCreateValues, thinkingEffort, adapterSchemaValues: { provider: "codex", modelReasoningEffort: "high" } }; - expect(buildPaperclipRunnerConfig(values).modelReasoningEffort).toBe(thinkingEffort || "high"); - expect(buildPaperclipRunnerConfig({ ...values, adapterSchemaValues: { provider: "acpx", acpxAgent: "claude", modelReasoningEffort: "high" } })).not.toHaveProperty("modelReasoningEffort"); - }); it.each(["", "--sandbox,read-only"])("shows actual automatic Legacy policy and preserves the explicit toggle (%s)", async (extraArgs) => { const container = document.createElement("div"); document.body.appendChild(container);