Fix Claude binary resolution for replace-mode command override
isAvailable() honored runtimeSettings.command.mode === "replace" but resolveClaudeBinary() only looked for `claude` on PATH. Users with a custom claude wrapper and no `claude` itself on PATH passed the availability check and then failed at session creation with "Claude binary not found". resolveClaudeBinary now mirrors isAvailable: if the runtime command is in replace mode, look up the override binary first.
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
import { createTestLogger } from "../../../../test-utils/test-logger.js";
|
||||
import * as executableUtils from "../../../../utils/executable.js";
|
||||
import {
|
||||
ClaudeAgentClient,
|
||||
convertClaudeHistoryEntry,
|
||||
@@ -14,6 +15,10 @@ interface TestClaudeSession {
|
||||
convertUsage(message: SDKMessage): AgentUsage | undefined;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("convertClaudeHistoryEntry", () => {
|
||||
test("maps user tool results to timeline items", () => {
|
||||
const toolUseId = "toolu_test";
|
||||
@@ -369,6 +374,59 @@ describe("ClaudeAgentClient.listModels", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("ClaudeAgentClient binary resolution", () => {
|
||||
const logger = createTestLogger();
|
||||
|
||||
test("uses the replace-command override binary when claude is not on PATH", async () => {
|
||||
const customClaudePath = "/path/to/custom-claude";
|
||||
vi.spyOn(executableUtils, "findExecutable").mockImplementation(async (name: string) => {
|
||||
if (name === "claude") {
|
||||
return null;
|
||||
}
|
||||
if (name === customClaudePath) {
|
||||
return customClaudePath;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const queryReturn = vi.fn();
|
||||
queryReturn.mockResolvedValue(undefined);
|
||||
const queryFactory = vi.fn(() => ({
|
||||
close: vi.fn(),
|
||||
return: queryReturn,
|
||||
}));
|
||||
|
||||
const client = new ClaudeAgentClient({
|
||||
logger,
|
||||
queryFactory,
|
||||
runtimeSettings: {
|
||||
command: {
|
||||
mode: "replace",
|
||||
argv: [customClaudePath],
|
||||
},
|
||||
},
|
||||
});
|
||||
const session = await client.createSession({
|
||||
provider: "claude",
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
|
||||
await expect(
|
||||
(
|
||||
session as unknown as {
|
||||
ensureQuery(): Promise<unknown>;
|
||||
}
|
||||
).ensureQuery(),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
expect(queryFactory.mock.calls[0]?.[0].options.pathToClaudeCodeExecutable).toBe(
|
||||
customClaudePath,
|
||||
);
|
||||
|
||||
await session.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeClaudeAskUserQuestionUpdatedInput", () => {
|
||||
test("maps frontend header-keyed answers to Claude question text keys", () => {
|
||||
expect(
|
||||
|
||||
@@ -1169,7 +1169,7 @@ export class ClaudeAgentClient implements AgentClient {
|
||||
this.logger = options.logger.child({ module: "agent", provider: "claude" });
|
||||
this.runtimeSettings = options.runtimeSettings;
|
||||
this.queryFactory = options.queryFactory;
|
||||
this.resolveBinary = options.resolveBinary ?? resolveClaudeBinary;
|
||||
this.resolveBinary = options.resolveBinary ?? (() => resolveClaudeBinary(this.runtimeSettings));
|
||||
}
|
||||
|
||||
async createSession(
|
||||
@@ -1296,7 +1296,15 @@ export class ClaudeAgentClient implements AgentClient {
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveClaudeBinary(): Promise<string> {
|
||||
async function resolveClaudeBinary(runtimeSettings?: ProviderRuntimeSettings): Promise<string> {
|
||||
const command = runtimeSettings?.command;
|
||||
if (command?.mode === "replace") {
|
||||
const foundOverride = await findExecutable(command.argv[0]);
|
||||
if (foundOverride) {
|
||||
return foundOverride;
|
||||
}
|
||||
}
|
||||
|
||||
const found = await findExecutable("claude");
|
||||
if (found) {
|
||||
return found;
|
||||
|
||||
Reference in New Issue
Block a user