diff --git a/packages/server/src/server/agent/providers/claude/agent.mode-catalog.test.ts b/packages/server/src/server/agent/providers/claude/agent.mode-catalog.test.ts new file mode 100644 index 0000000000..1c6d175b65 --- /dev/null +++ b/packages/server/src/server/agent/providers/claude/agent.mode-catalog.test.ts @@ -0,0 +1,54 @@ +import { expect, test } from "vitest"; + +import { createTestLogger } from "../../../../test-utils/test-logger.js"; +import { ClaudeAgentClient } from "./agent.js"; + +const hostedEnvironments = [ + { transport: "Bedrock", env: { CLAUDE_CODE_USE_BEDROCK: "1" } }, + { transport: "Vertex", env: { CLAUDE_CODE_USE_VERTEX: "true" } }, +]; + +test.each(hostedEnvironments)( + "offers Auto mode on $transport while preserving its default mode", + async ({ env }) => { + const client = new ClaudeAgentClient({ + logger: createTestLogger(), + runtimeSettings: { env }, + resolveVersion: async () => "2.1.263", + discoverModels: async () => [], + }); + + const catalog = await client.fetchCatalog({ cwd: process.cwd() }); + + expect(catalog.modes?.map((mode) => mode.id)).toContain("auto"); + expect(catalog.defaultModeId).toBe("default"); + }, +); + +test.each(hostedEnvironments)( + "passes an explicit Auto selection on $transport to Claude Code", + async ({ env }) => { + const sdkError = new Error("Claude Code rejected this request"); + let requestedMode: string | undefined; + let requestedEnv: NodeJS.ProcessEnv | undefined; + const client = new ClaudeAgentClient({ + logger: createTestLogger(), + runtimeSettings: { env }, + resolveBinary: async () => "/test/claude", + queryFactory: ({ options }) => { + requestedMode = options.permissionMode; + requestedEnv = options.env; + throw sdkError; + }, + }); + const session = await client.createSession({ provider: "claude", cwd: process.cwd() }); + + try { + await expect(session.setMode("auto")).rejects.toBe(sdkError); + expect(requestedMode).toBe("auto"); + expect(requestedEnv).toMatchObject(env); + } finally { + await session.close(); + } + }, +); diff --git a/packages/server/src/server/agent/providers/claude/agent.redesign.test.ts b/packages/server/src/server/agent/providers/claude/agent.redesign.test.ts index 5393926520..3d88f7d15f 100644 --- a/packages/server/src/server/agent/providers/claude/agent.redesign.test.ts +++ b/packages/server/src/server/agent/providers/claude/agent.redesign.test.ts @@ -201,23 +201,6 @@ test("exposes and applies auto permission mode", async () => { } }); -test("rejects auto mode when Claude Code uses Bedrock", async () => { - const previousBedrock = process.env.CLAUDE_CODE_USE_BEDROCK; - process.env.CLAUDE_CODE_USE_BEDROCK = "1"; - - const session = await createSession(); - - try { - await expect(session.setMode("auto")).rejects.toThrow( - "Claude Auto mode requires the Anthropic API and is not supported when Claude Code uses Bedrock", - ); - expect(sdkQueryFactory).not.toHaveBeenCalled(); - } finally { - restoreEnvValue("CLAUDE_CODE_USE_BEDROCK", previousBedrock); - await session.close(); - } -}); - test.each([ { env: {}, expected: "auto" }, { env: { CLAUDE_CODE_USE_BEDROCK: "1" }, expected: "default" }, @@ -293,40 +276,6 @@ test("allows launch env to disable inherited Bedrock transport for auto mode", a } }); -test("fails an auto mode turn when Claude Code uses Vertex", async () => { - const previousVertex = process.env.CLAUDE_CODE_USE_VERTEX; - process.env.CLAUDE_CODE_USE_VERTEX = "true"; - sdkQueryFactory.mockImplementation(() => { - throw new Error("query should not start"); - }); - const client = new ClaudeAgentClient({ - logger: createTestLogger(), - queryFactory: sdkQueryFactory, - resolveBinary: async () => "/test/claude/bin", - }); - const session = await client.createSession({ - provider: "claude", - cwd: process.cwd(), - modeId: "auto", - }); - - try { - const events = await collectUntilTerminal(streamSession(session, "hello")); - const failure = events.find( - (event): event is Extract => - event.type === "turn_failed", - ); - - expect(failure?.error).toContain( - "Claude Auto mode requires the Anthropic API and is not supported when Claude Code uses Vertex", - ); - expect(sdkQueryFactory).not.toHaveBeenCalled(); - } finally { - restoreEnvValue("CLAUDE_CODE_USE_VERTEX", previousVertex); - await session.close(); - } -}); - test("switches an auto mode session to another mode when Claude Code uses Bedrock", async () => { const previousBedrock = process.env.CLAUDE_CODE_USE_BEDROCK; process.env.CLAUDE_CODE_USE_BEDROCK = "1"; diff --git a/packages/server/src/server/agent/providers/claude/agent.ts b/packages/server/src/server/agent/providers/claude/agent.ts index 8ea12610ea..10e71b100f 100644 --- a/packages/server/src/server/agent/providers/claude/agent.ts +++ b/packages/server/src/server/agent/providers/claude/agent.ts @@ -947,37 +947,11 @@ function isTruthyEnvValue(value: string | undefined): boolean { ); } -function claudeAutoModeUnavailableOn(env: NodeJS.ProcessEnv): "Bedrock" | "Vertex" | null { - if (isTruthyEnvValue(env.CLAUDE_CODE_USE_BEDROCK)) { - return "Bedrock"; - } - if (isTruthyEnvValue(env.CLAUDE_CODE_USE_VERTEX)) { - return "Vertex"; - } - return null; -} - -function assertClaudeModeCanRun(mode: PermissionMode, env: NodeJS.ProcessEnv): void { - if (mode !== "auto") { - return; - } - const transport = claudeAutoModeUnavailableOn(env); - if (transport === null) { - return; - } - throw new Error( - `Claude Auto mode requires the Anthropic API and is not supported when Claude Code uses ${transport}. Select another permission mode or unset the ${transport === "Bedrock" ? "CLAUDE_CODE_USE_BEDROCK" : "CLAUDE_CODE_USE_VERTEX"} environment variable.`, - ); -} - -function claudeModeCatalog(env: NodeJS.ProcessEnv): { - modes: AgentMode[]; - defaultModeId: PermissionMode; -} { - if (claudeAutoModeUnavailableOn(env)) { - return { modes: DEFAULT_MODES.filter((mode) => mode.id !== "auto"), defaultModeId: "default" }; - } - return { modes: DEFAULT_MODES, defaultModeId: "auto" }; +function claudeDefaultMode(env: NodeJS.ProcessEnv): PermissionMode { + return isTruthyEnvValue(env.CLAUDE_CODE_USE_BEDROCK) || + isTruthyEnvValue(env.CLAUDE_CODE_USE_VERTEX) + ? "default" + : "auto"; } function coerceSessionMetadata(metadata: AgentMetadata | undefined): Partial { @@ -1642,15 +1616,15 @@ export class ClaudeAgentClient implements AgentClient { discoveredModelIds, ), ); - const modeCatalog = claudeModeCatalog(env); return { models, - ...modeCatalog, + modes: DEFAULT_MODES, + defaultModeId: claudeDefaultMode(env), }; } async resolveDefaultModeId({ env: launchEnv }: ResolveAgentDefaultModeInput): Promise { - return claudeModeCatalog(this.buildProviderEnv(launchEnv)).defaultModeId; + return claudeDefaultMode(this.buildProviderEnv(launchEnv)); } private buildProviderEnv(launchEnv?: Record): NodeJS.ProcessEnv { @@ -2540,7 +2514,6 @@ class ClaudeAgentSession implements AgentSession { } const normalized = isPermissionMode(modeId) ? modeId : "default"; - assertClaudeModeCanRun(normalized, this.harnessEnvironment); const previousMode = this.currentMode; const launchesQuery = !this.query || this.queryRestartNeeded; const activeQuery = await this.ensureQuery(normalized); @@ -3424,7 +3397,6 @@ class ClaudeAgentSession implements AgentSession { ); const settingsOptions = this.buildSettingsOptions(providerOptions, { ultracode }); const sdkEnv = this.harnessEnvironment; - assertClaudeModeCanRun(permissionMode, sdkEnv); const claudeBinary = await this.resolveBinary(); this.logger.debug(