From f6c2906b78ed620cb66f69d63fa27717605a8e8e Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 14 Oct 2026 22:13:20 -0500 Subject: [PATCH] fix(runner): qualify Claude native executable on Linux ARM64 Verify the pinned 0.3.286 platform package, executable digest, and declared npm dependency graph using the existing custody checks. Co-Authored-By: Paperclip --- .../src/drivers/acpx/installation-integrity.test.ts | 6 +++--- .../src/drivers/acpx/installation-integrity.ts | 13 +++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts index f9f014ee7b3..a1a881d080c 100644 --- a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts @@ -54,7 +54,7 @@ afterEach(async () => { }); describe("ACPX installation integrity", () => { - it.each([["linux", "x64"], ["darwin", "arm64"], ["darwin", "x64"]] as const)( + it.each([["linux", "x64"], ["linux", "arm64"], ["darwin", "arm64"], ["darwin", "x64"]] as const)( "retains Claude executable admission for a published bundle on %s %s", async (os, cpu) => { const parent = await realpath(await mkdtemp(join(tmpdir(), "paperclip-claude-npm-admission-"))); temporaryDirectories.push(parent); @@ -223,7 +223,7 @@ describe("ACPX installation integrity", () => { } }); - it.each([["linux", "arm64"], ["darwin", "ia32"], ["freebsd", "x64"]] as const)( + it.each([["linux", "ia32"], ["darwin", "ia32"], ["freebsd", "x64"]] as const)( "rejects the actual Claude runtime probe on unsupported %s %s", async (platform, arch) => { const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue(platform); @@ -747,7 +747,7 @@ describe("ACPX installation integrity", () => { ); }); - it.runIf((process.platform === "linux" && process.arch === "x64") || (process.platform === "darwin" && ["arm64", "x64"].includes(process.arch)))( + it.runIf(["linux", "darwin"].includes(process.platform) && ["arm64", "x64"].includes(process.arch))( "resolves and pins the installed Claude ACP dependency graph", async () => { const profile = resolveQualifiedAcpxProfile("claude", "claude-sonnet-5"); diff --git a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts index e96722c9759..eaa1da855ef 100644 --- a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts +++ b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts @@ -61,6 +61,12 @@ const QUALIFIED_CLAUDE_LINUX_X64_RUNTIME = Object.freeze({ environmentVariable: "CLAUDE_CODE_EXECUTABLE", }); +const QUALIFIED_CLAUDE_LINUX_ARM64_RUNTIME = Object.freeze({ + ...QUALIFIED_CLAUDE_LINUX_X64_RUNTIME, + packageName: "@anthropic-ai/claude-agent-sdk-linux-arm64", + executableDigest: "sha256:0292fa22ac2fd43e16be9d0e511ddd8347280d6e0ebaca744ef5b27e05d8d0f8", +}); + const QUALIFIED_CLAUDE_DARWIN_RUNTIMES = { arm64: Object.freeze({ ...QUALIFIED_CLAUDE_LINUX_X64_RUNTIME, @@ -389,6 +395,7 @@ function isDeclaredHoistedProviderPlatform(root: string, manifest: string, issue const platformQualification = [ { qualified: QUALIFIED_CODEX_LINUX_X64_RUNTIME, os: "linux", cpu: "x64", alias: true }, { qualified: QUALIFIED_CLAUDE_LINUX_X64_RUNTIME, os: "linux", cpu: "x64", alias: false }, + { qualified: QUALIFIED_CLAUDE_LINUX_ARM64_RUNTIME, os: "linux", cpu: "arm64", alias: false }, { qualified: QUALIFIED_CLAUDE_DARWIN_RUNTIMES.arm64, os: "darwin", cpu: "arm64", alias: false }, { qualified: QUALIFIED_CLAUDE_DARWIN_RUNTIMES.x64, os: "darwin", cpu: "x64", alias: false }, ].find(value => value.qualified.packageName === packageName); @@ -1020,7 +1027,9 @@ async function verifyQualifiedRuntimeExecutable(input: { input.profile.agent === "claude" ? process.platform === "darwin" && (process.arch === "arm64" || process.arch === "x64") ? QUALIFIED_CLAUDE_DARWIN_RUNTIMES[process.arch] - : QUALIFIED_CLAUDE_LINUX_X64_RUNTIME + : process.platform === "linux" && process.arch === "arm64" + ? QUALIFIED_CLAUDE_LINUX_ARM64_RUNTIME + : QUALIFIED_CLAUDE_LINUX_X64_RUNTIME : input.profile.agent === "codex" ? QUALIFIED_CODEX_LINUX_X64_RUNTIME : null; @@ -1034,7 +1043,7 @@ async function verifyQualifiedRuntimeExecutable(input: { ); } if (!((process.platform === "linux" && process.arch === "x64") - || (input.profile.agent === "claude" && process.platform === "darwin" && (process.arch === "arm64" || process.arch === "x64")))) { + || (input.profile.agent === "claude" && ["linux", "darwin"].includes(process.platform) && (process.arch === "arm64" || process.arch === "x64")))) { throw new Error( `ACPX ${input.profile.agent} verified runtime executable is unavailable for ${process.platform} ${process.arch}`, );