Let a Claude agent leave Auto mode when Claude Code uses Bedrock or Vertex (#6272)

* fix(claude): let an agent leave Auto mode when Claude Code uses Bedrock or Vertex

Changing the mode of a Claude agent whose process is not running starts the
process first. It was started in the agent's current mode, so an agent in Auto
on Bedrock or Vertex failed the Auto check before the new mode applied, and
every mode change and message failed the same way.

Start the process in the requested mode instead.

Closes #5878

* fix(claude): relaunch a query whose mode change Claude Code rejected

A mode change that starts the query now launches it in the requested mode. If
Claude Code then rejects the mode, the agent keeps its previous mode, so mark
that query for relaunch instead of reusing it in the rejected mode.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
paseo-bot[bot]
2026-10-07 04:55:01 +02:00
committed by GitHub
parent 4dd0e50e78
commit 8f1091da64
2 changed files with 67 additions and 9 deletions
@@ -327,6 +327,57 @@ test("fails an auto mode turn when Claude Code uses Vertex", async () => {
}
});
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";
const queryMock = createBaseQueryMock(vi.fn(async () => ({ done: true, value: undefined })));
sdkQueryFactory.mockImplementation(() => queryMock);
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 {
await session.setMode("acceptEdits");
expect(await session.getCurrentMode()).toBe("acceptEdits");
expect(sdkQueryFactory).toHaveBeenCalledTimes(1);
expect(sdkQueryFactory.mock.calls[0]?.[0]?.options?.permissionMode).toBe("acceptEdits");
} finally {
restoreEnvValue("CLAUDE_CODE_USE_BEDROCK", previousBedrock);
await session.close();
}
});
test("does not keep a query started in a mode that Claude Code rejected", async () => {
const launchedModes: Array<string | undefined> = [];
sdkQueryFactory.mockImplementation(({ options }: { options: { permissionMode?: string } }) => {
launchedModes.push(options.permissionMode);
const queryMock = createBaseQueryMock(vi.fn(async () => ({ done: true, value: undefined })));
if (launchedModes.length === 1) {
queryMock.setPermissionMode.mockRejectedValue(new Error("bypass refused"));
}
return queryMock;
});
const session = await createSession();
try {
await expect(session.setMode("bypassPermissions")).rejects.toThrow("bypass refused");
await session.listCommands();
expect(await session.getCurrentMode()).toBe("default");
expect(launchedModes).toEqual(["bypassPermissions", "default"]);
} finally {
await session.close();
}
});
test("logs redacted query summary and never leaks sentinel secrets", async () => {
const envSecret = "PASEO_ENV_SENTINEL_SECRET";
const runtimeSecret = "PASEO_RUNTIME_SENTINEL_SECRET";
@@ -1037,11 +1088,11 @@ test("preserves bypass capability across query restarts triggered by thinking ch
expect(capturedOptions).toHaveLength(2);
expect(capturedOptions[0]).toMatchObject({
permissionMode: "default",
permissionMode: "bypassPermissions",
allowDangerouslySkipPermissions: true,
});
expect(capturedOptions[1]).toMatchObject({
permissionMode: "acceptEdits",
permissionMode: "bypassPermissions",
allowDangerouslySkipPermissions: true,
effort: "high",
});
@@ -2441,8 +2441,15 @@ class ClaudeAgentSession implements AgentSession {
const normalized = isPermissionMode(modeId) ? modeId : "default";
assertClaudeModeCanRun(normalized, this.harnessEnvironment);
const previousMode = this.currentMode;
const activeQuery = await this.ensureQuery();
await activeQuery.setPermissionMode(normalized);
const launchesQuery = !this.query || this.queryRestartNeeded;
const activeQuery = await this.ensureQuery(normalized);
try {
await activeQuery.setPermissionMode(normalized);
} catch (error) {
// The query was launched in the rejected mode; relaunch in the current mode next time.
if (launchesQuery) this.queryRestartNeeded = true;
throw error;
}
if (normalized === "plan") {
if (previousMode !== "plan") {
this.planResumeMode = previousMode;
@@ -3128,7 +3135,7 @@ class ClaudeAgentSession implements AgentSession {
return { kind: "fresh-session" };
}
private async ensureQuery(): Promise<Query> {
private async ensureQuery(launchMode: PermissionMode = this.currentMode): Promise<Query> {
if (this.query && !this.queryRestartNeeded) {
return this.query;
}
@@ -3172,7 +3179,7 @@ class ClaudeAgentSession implements AgentSession {
this.persistence = null;
const input = createAsyncMessageInput<SDKUserMessage>();
const options = await this.buildOptions();
const options = await this.buildOptions(launchMode);
this.logger.debug({ options: summarizeClaudeOptionsForLog(options) }, "claude query");
this.input = input;
this.query = claudeQuery(
@@ -3298,7 +3305,7 @@ class ClaudeAgentSession implements AgentSession {
});
}
private async buildOptions(): Promise<ClaudeOptions> {
private async buildOptions(permissionMode: PermissionMode): Promise<ClaudeOptions> {
const { thinking, effort, ultracode } = this.resolveThinkingConfig();
const appendedSystemPrompt = this.buildAppendedSystemPrompt();
const providerOptions = applyClaudeToolPolicy(
@@ -3307,7 +3314,7 @@ class ClaudeAgentSession implements AgentSession {
);
const settingsOptions = this.buildSettingsOptions(providerOptions, { ultracode });
const sdkEnv = this.harnessEnvironment;
assertClaudeModeCanRun(this.currentMode, sdkEnv);
assertClaudeModeCanRun(permissionMode, sdkEnv);
const claudeBinary = await this.resolveBinary();
this.logger.debug(
@@ -3330,7 +3337,7 @@ class ClaudeAgentSession implements AgentSession {
const base: ClaudeOptions = {
cwd: this.config.cwd,
includePartialMessages: true,
permissionMode: this.currentMode,
permissionMode,
// Dynamic mode switching can recreate the underlying Claude query. Keep the
// bypass launch capability available so later setPermissionMode("bypassPermissions")
// calls do not fail after a model/thinking/rewind-driven restart.