From 3eee842164e79f9ca2d3ccab591ab1a8e287daa9 Mon Sep 17 00:00:00 2001 From: ejclaw Date: Fri, 24 Apr 2026 13:13:11 +0900 Subject: [PATCH] fix: restore bundled agent CLI resolution - Upgrade EJClaw bundled Codex CLI to 0.124.0 for gpt-5.5 access - Resolve Claude Agent SDK native binary packages via package.json - Prefer glibc Claude binary before musl on Linux and align platform binary names - Add regression coverage for optional package resolution --- runners/agent-runner/src/bundled-cli-path.ts | 21 +++++-- .../test/bundled-cli-path.test.ts | 55 +++++++++++++++++-- runners/codex-runner/bun.lock | 16 +++--- runners/codex-runner/package.json | 2 +- 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/runners/agent-runner/src/bundled-cli-path.ts b/runners/agent-runner/src/bundled-cli-path.ts index 281bf70..f48a3f1 100644 --- a/runners/agent-runner/src/bundled-cli-path.ts +++ b/runners/agent-runner/src/bundled-cli-path.ts @@ -45,7 +45,7 @@ function platformCandidates( return [ { pkg: `@anthropic-ai/claude-agent-sdk-darwin-${arch}`, - file: arch === 'arm64' ? 'cli' : 'cli', + file: 'claude', }, ]; } @@ -53,7 +53,7 @@ function platformCandidates( return [ { pkg: `@anthropic-ai/claude-agent-sdk-win32-${arch}`, - file: 'cli.exe', + file: 'claude.exe', }, ]; } @@ -127,12 +127,22 @@ export function resolveBundledClaudeCodeExecutable(options?: { * Default package directory resolver. Uses `require.resolve` against this * module's location so it works regardless of whether agent-runner is invoked * from its own node_modules layout or via a parent workspace. + * + * Important: the SDK's optional native-binary packages may not expose a bare + * package entrypoint, so `require.resolve(pkg)` can fail even when the package + * and binary are installed. Resolve `package.json` first, then fall back to the + * bare package only for package layouts that do expose an entrypoint. */ function defaultResolvePackageDir(pkg: string): string | null { + const req = createRequire(import.meta.url); + try { + const pkgJson = req.resolve(`${pkg}/package.json`); + return path.dirname(pkgJson); + } catch { + // Fall through to legacy/bare-entrypoint resolution below. + } + try { - // Resolve the package entrypoint instead of package.json because the SDK's - // exports map does not expose `./package.json`. - const req = createRequire(import.meta.url); const entrypoint = req.resolve(pkg); return path.dirname(entrypoint); } catch { @@ -142,5 +152,6 @@ function defaultResolvePackageDir(pkg: string): string | null { export const __test__ = { platformCandidates, + defaultResolvePackageDir, ENV_OVERRIDE, }; diff --git a/runners/agent-runner/test/bundled-cli-path.test.ts b/runners/agent-runner/test/bundled-cli-path.test.ts index 608fbf7..a68c8b3 100644 --- a/runners/agent-runner/test/bundled-cli-path.test.ts +++ b/runners/agent-runner/test/bundled-cli-path.test.ts @@ -1,8 +1,12 @@ import fs from 'fs'; +import { createRequire } from 'module'; import path from 'path'; import { afterEach, describe, expect, it } from 'vitest'; -import { resolveBundledClaudeCodeExecutable } from '../src/bundled-cli-path.js'; +import { + __test__, + resolveBundledClaudeCodeExecutable, +} from '../src/bundled-cli-path.js'; describe('resolveBundledClaudeCodeExecutable', () => { const origEnv = process.env.EJCLAW_CLAUDE_CLI_PATH; @@ -83,10 +87,10 @@ describe('resolveBundledClaudeCodeExecutable', () => { expect(result).toBe(path.join(muslDir, 'claude')); }); - it('resolves Darwin arm64 binary under `cli`', () => { + it('resolves Darwin arm64 binary under `claude`', () => { const dir = '/fake/node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64'; - const existsSync = (p: string): boolean => p === path.join(dir, 'cli'); + const existsSync = (p: string): boolean => p === path.join(dir, 'claude'); const result = resolveBundledClaudeCodeExecutable({ env: {}, existsSync, @@ -95,13 +99,13 @@ describe('resolveBundledClaudeCodeExecutable', () => { resolvePackageDir: (pkg) => pkg === '@anthropic-ai/claude-agent-sdk-darwin-arm64' ? dir : null, }); - expect(result).toBe(path.join(dir, 'cli')); + expect(result).toBe(path.join(dir, 'claude')); }); - it('resolves Windows binary under `cli.exe`', () => { + it('resolves Windows binary under `claude.exe`', () => { const dir = 'C:\\\\fake\\\\node_modules\\\\@anthropic-ai\\\\claude-agent-sdk-win32-x64'; - const binary = path.join(dir, 'cli.exe'); + const binary = path.join(dir, 'claude.exe'); const existsSync = (p: string): boolean => p === binary; const result = resolveBundledClaudeCodeExecutable({ env: {}, @@ -114,6 +118,17 @@ describe('resolveBundledClaudeCodeExecutable', () => { expect(result).toBe(binary); }); + it('default resolver handles SDK optional packages without a bare entrypoint', () => { + if (process.platform !== 'linux' || process.arch !== 'x64') return; + + const dir = __test__.defaultResolvePackageDir( + '@anthropic-ai/claude-agent-sdk-linux-x64', + ); + if (!dir) return; + + expect(fs.existsSync(path.join(dir, 'claude'))).toBe(true); + }); + it('throws a descriptive error when no binary is found', () => { const existsSync = () => false; expect(() => @@ -127,6 +142,34 @@ describe('resolveBundledClaudeCodeExecutable', () => { ).toThrow(/Unable to locate a bundled Claude Code CLI binary/); }); + it('resolves the linux x64 optional package even when it has no JS entrypoint', () => { + if (process.platform !== 'linux' || process.arch !== 'x64') { + return; + } + + const req = createRequire(import.meta.url); + let packageJsonPath: string; + try { + packageJsonPath = req.resolve( + '@anthropic-ai/claude-agent-sdk-linux-x64/package.json', + ); + } catch { + return; + } + + try { + req.resolve('@anthropic-ai/claude-agent-sdk-linux-x64'); + return; + } catch { + // This is the regression case: package.json resolves, bare package does not. + } + + const result = resolveBundledClaudeCodeExecutable({ env: {} }); + + expect(result).toBe(path.join(path.dirname(packageJsonPath), 'claude')); + expect(fs.existsSync(result)).toBe(true); + }); + it('end-to-end: resolves the actual bundled binary on this host when installed', () => { // Real-world smoke test. Some CI/host sandboxes do not install the optional // per-platform SDK package, so treat "package not resolvable" as a host diff --git a/runners/codex-runner/bun.lock b/runners/codex-runner/bun.lock index f4e3765..efcfdf2 100644 --- a/runners/codex-runner/bun.lock +++ b/runners/codex-runner/bun.lock @@ -5,7 +5,7 @@ "": { "name": "ejclaw-codex-runner", "dependencies": { - "@openai/codex": "^0.120.0", + "@openai/codex": "^0.124.0", "ejclaw-runners-shared": "file:../shared", }, "devDependencies": { @@ -15,19 +15,19 @@ }, }, "packages": { - "@openai/codex": ["@openai/codex@0.120.0", "", { "optionalDependencies": { "@openai/codex-darwin-arm64": "npm:@openai/codex@0.120.0-darwin-arm64", "@openai/codex-darwin-x64": "npm:@openai/codex@0.120.0-darwin-x64", "@openai/codex-linux-arm64": "npm:@openai/codex@0.120.0-linux-arm64", "@openai/codex-linux-x64": "npm:@openai/codex@0.120.0-linux-x64", "@openai/codex-win32-arm64": "npm:@openai/codex@0.120.0-win32-arm64", "@openai/codex-win32-x64": "npm:@openai/codex@0.120.0-win32-x64" }, "bin": { "codex": "bin/codex.js" } }, "sha512-e2P1Gya3dwsRe9IPOiswVz5JfR700u+/sWCqDc3jkqv2QViPkNiBmZoGhFnZL5jBpKakSjehC4/Fpspg70nHTw=="], + "@openai/codex": ["@openai/codex@0.124.0", "", { "optionalDependencies": { "@openai/codex-darwin-arm64": "npm:@openai/codex@0.124.0-darwin-arm64", "@openai/codex-darwin-x64": "npm:@openai/codex@0.124.0-darwin-x64", "@openai/codex-linux-arm64": "npm:@openai/codex@0.124.0-linux-arm64", "@openai/codex-linux-x64": "npm:@openai/codex@0.124.0-linux-x64", "@openai/codex-win32-arm64": "npm:@openai/codex@0.124.0-win32-arm64", "@openai/codex-win32-x64": "npm:@openai/codex@0.124.0-win32-x64" }, "bin": { "codex": "bin/codex.js" } }, "sha512-1EVAuPyAQZ8zIVMw3bPJ6a4R8ifLAZ7LGsOyknj5c2he9AFXVRCmWx12WrdZJ25wcBvOEKt1n1Zx+QAj0EVGbQ=="], - "@openai/codex-darwin-arm64": ["@openai/codex@0.120.0-darwin-arm64", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7CU+I5kBaMuoqfG3xisq0mUWzxoEHvfu34cB8a0KpBiIhAgu12fKpmYgZ4/DvRP6Wm9Fu6LJYKVF5apUHFp8nQ=="], + "@openai/codex-darwin-arm64": ["@openai/codex@0.124.0-darwin-arm64", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lnuqeAdl+RjPWsqVZ8rrPskRIOZmzlyr8e5q/wFVEnMPsm9dWgjRW1PKa84UmDSQVOuz9GObF28DEHFyC4A8NA=="], - "@openai/codex-darwin-x64": ["@openai/codex@0.120.0-darwin-x64", "", { "os": "darwin", "cpu": "x64" }, "sha512-d7joNYuwrmd5iIdp/xAE5f8bZT1r82MnmU6Hzgxq3G+xClwEyhxU737ZWnstFSpnZNfxJ5zXCuFUJh4CAkHNtQ=="], + "@openai/codex-darwin-x64": ["@openai/codex@0.124.0-darwin-x64", "", { "os": "darwin", "cpu": "x64" }, "sha512-Br+VlL83IOu96Urw+mkZ1PQXLsQXGAYEH6kXNt4ULuVt7qAdQY2FKYwIgLLVLl0vpMk8qWxdfdVMqhiu2uCHlg=="], - "@openai/codex-linux-arm64": ["@openai/codex@0.120.0-linux-arm64", "", { "os": "linux", "cpu": "arm64" }, "sha512-sVYY25/URlpZPtb0Q0ryLh+lcq9UTEtHAkdZKa0a/R7mAdyPuhpU9V6jWmxwiUh7s53XZOEVFoKmLfH8YIDWCQ=="], + "@openai/codex-linux-arm64": ["@openai/codex@0.124.0-linux-arm64", "", { "os": "linux", "cpu": "arm64" }, "sha512-QXafFVQJxPfU1LSCI5afuHsF5evKAcbQpFuKou0Kl241/HG/zYHdwoWj546zH844gJgegIPAxPf36+RSkUsbbA=="], - "@openai/codex-linux-x64": ["@openai/codex@0.120.0-linux-x64", "", { "os": "linux", "cpu": "x64" }, "sha512-VcP9B/c/O+EFEgqoetCzvHrLfAdo8vrt09Gx1lJ8ikewctqAuJ/ozj/6wuvlz7XaaK64ib5cge01pOAeCyt2Sg=="], + "@openai/codex-linux-x64": ["@openai/codex@0.124.0-linux-x64", "", { "os": "linux", "cpu": "x64" }, "sha512-cJ4QfQIrz2gkXVWephiGo9JDyD3qLKhvkTTLk7gI8rKd7MZpVXn9/1e7pZCQnJVA7Dk6ocA5G+sxnR78SoIejw=="], - "@openai/codex-win32-arm64": ["@openai/codex@0.120.0-win32-arm64", "", { "os": "win32", "cpu": "arm64" }, "sha512-SAaTQU1XHa1qDnmQldmbyROIY5SiaspF+Cw3ziWeeTgyAET3rWusm4ELOElx6QiY1ugYW5ZD+7AFufS2z1xtpQ=="], + "@openai/codex-win32-arm64": ["@openai/codex@0.124.0-win32-arm64", "", { "os": "win32", "cpu": "arm64" }, "sha512-oDSI/CQh0kagBwWVPG9EiUBfHiY27ju3LPrjTVZg4VMpVJVNA31JTK8rHMZ6G/2gfNmOl6DMBA6+0ICxSkkshg=="], - "@openai/codex-win32-x64": ["@openai/codex@0.120.0-win32-x64", "", { "os": "win32", "cpu": "x64" }, "sha512-zja1GNrbHyOUTvOy5FVMa+rAYIs3m+FOS8rAXftxMEhodMmkMw2O8zcvso657SHhZR0hIEiZ6T70lcyH2YX0mQ=="], + "@openai/codex-win32-x64": ["@openai/codex@0.124.0-win32-x64", "", { "os": "win32", "cpu": "x64" }, "sha512-t+lAwmCWwIWQKk6dKaYetRhQsmA+uDmQy1NLka1xAAv3PlNOH8iNz9Lsisr3qYkBR8Tf7tbQlt+pl9tw/A5mfA=="], "@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="], diff --git a/runners/codex-runner/package.json b/runners/codex-runner/package.json index 0dfb377..66788ca 100644 --- a/runners/codex-runner/package.json +++ b/runners/codex-runner/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "ejclaw-runners-shared": "file:../shared", - "@openai/codex": "^0.120.0" + "@openai/codex": "^0.124.0" }, "devDependencies": { "@types/node": "^22.10.7",