diff --git a/packages/server/src/server/agent/providers/claude/agent.test.ts b/packages/server/src/server/agent/providers/claude/agent.test.ts index d07b0572b7..d6b869f1e0 100644 --- a/packages/server/src/server/agent/providers/claude/agent.test.ts +++ b/packages/server/src/server/agent/providers/claude/agent.test.ts @@ -575,6 +575,36 @@ describe("ClaudeAgentClient binary resolution", () => { await expect(resolveClaudeCodeVersion()).resolves.toMatch(/^\d+\.\d+\.\d+$/); }); + test("rejects a wrapper command's own version output instead of reading it as Claude Code's", async () => { + const wrapperDir = await fs.mkdtemp(path.join(os.tmpdir(), "paseo-claude-version-")); + const wrapperPath = path.join(wrapperDir, "wrapper"); + await fs.writeFile(wrapperPath, 'console.log("cux 0.4.1");\n'); + try { + await expect( + resolveClaudeCodeVersion({ + command: { mode: "replace", argv: [process.execPath, wrapperPath] }, + }), + ).rejects.toThrow("Unable to parse Claude Code version from --version output"); + } finally { + await fs.rm(wrapperDir, { recursive: true, force: true }); + } + }); + + test("resolves the version when the command forwards the Claude Code banner", async () => { + const wrapperDir = await fs.mkdtemp(path.join(os.tmpdir(), "paseo-claude-version-")); + const wrapperPath = path.join(wrapperDir, "wrapper"); + await fs.writeFile(wrapperPath, 'console.log("2.1.302 (Claude Code)");\n'); + try { + await expect( + resolveClaudeCodeVersion({ + command: { mode: "replace", argv: [process.execPath, wrapperPath] }, + }), + ).resolves.toBe("2.1.302"); + } finally { + await fs.rm(wrapperDir, { recursive: true, force: true }); + } + }); + test("loads user, project, and local Claude settings", async () => { const queryReturn = vi.fn(); queryReturn.mockResolvedValue(undefined); diff --git a/packages/server/src/server/agent/providers/claude/agent.ts b/packages/server/src/server/agent/providers/claude/agent.ts index 7517a87029..b12ab706da 100644 --- a/packages/server/src/server/agent/providers/claude/agent.ts +++ b/packages/server/src/server/agent/providers/claude/agent.ts @@ -1839,7 +1839,13 @@ export async function resolveClaudeCodeVersion( timeout: 5_000, signal, }); - const version = parseClaudeCodeVersion(`${stdout}\n${stderr}`); + // A wrapper command (the `command` provider setting) prints its own version here + // instead of Claude Code's. Only trust output that identifies itself as Claude Code, + // so a wrapper's version can't mis-gate models behind a minimumClaudeCodeVersion. + const rawVersionOutput = `${stdout}\n${stderr}`; + const version = /\d+\.\d+\.\d+\s+\(Claude Code\)/i.test(rawVersionOutput) + ? parseClaudeCodeVersion(rawVersionOutput) + : null; if (!version) { throw new Error("Unable to parse Claude Code version from --version output"); } diff --git a/packages/server/src/server/agent/providers/claude/models.test.ts b/packages/server/src/server/agent/providers/claude/models.test.ts index 16d13b9d68..0daa5e533a 100644 --- a/packages/server/src/server/agent/providers/claude/models.test.ts +++ b/packages/server/src/server/agent/providers/claude/models.test.ts @@ -559,6 +559,33 @@ describe("ClaudeAgentClient.fetchCatalog", () => { expect(models.map((model) => model.id)).not.toContain("claude-opus-5[1m]"); expect(models.map((model) => model.id)).not.toContain("claude-opus-5"); }); + + it("keeps version-gated models when the probe command cannot identify Claude Code", async () => { + const configDir = await createClaudeConfigDir({}); + const wrapperDir = await fs.mkdtemp(path.join(os.tmpdir(), "paseo-claude-wrapper-")); + const wrapperPath = path.join(wrapperDir, "wrapper"); + await fs.writeFile(wrapperPath, 'console.log("cux 0.4.1");\n'); + try { + const client = new ClaudeAgentClient({ + logger: createTestLogger(), + runtimeSettings: { + command: { mode: "replace", argv: [process.execPath, wrapperPath] }, + env: { CLAUDE_CONFIG_DIR: configDir }, + }, + discoverModels: async () => [], + }); + + const { models } = await client.fetchCatalog({ + scope: "workspace", + cwd: configDir, + force: false, + }); + + expect(models.map((model) => model.id)).toContain("claude-opus-5-5"); + } finally { + await fs.rm(wrapperDir, { recursive: true, force: true }); + } + }); }); describe("normalizeClaudeRuntimeModelId", () => {