diff --git a/packages/agent/src/adapters/claude/permissions/permission-handlers.test.ts b/packages/agent/src/adapters/claude/permissions/permission-handlers.test.ts index 528a219e18..3137059271 100644 --- a/packages/agent/src/adapters/claude/permissions/permission-handlers.test.ts +++ b/packages/agent/src/adapters/claude/permissions/permission-handlers.test.ts @@ -345,3 +345,50 @@ describe("canUseTool MCP approval enforcement", () => { ); }); }); + +describe("canUseTool auto mode hands-off approval", () => { + beforeEach(() => { + clearMcpToolMetadataCache(); + }); + + // Auto mode advertises hands-off approval; it must not prompt for the write + // and shell tools that drive every real task. + it.each(["Bash", "BashOutput", "KillShell", "Edit", "Write", "NotebookEdit"])( + "auto-allows %s in auto mode without prompting", + async (toolName) => { + const context = createContext(toolName, { + session: { + permissionMode: "auto", + settingsManager: { + getRepoRoot: vi.fn().mockReturnValue("/repo"), + }, + }, + }); + + const result = await canUseTool(context); + + expect(result.behavior).toBe("allow"); + expect(context.client.requestPermission).not.toHaveBeenCalled(); + }, + ); + + // Guard against the fix leaking into default mode, where these tools should + // still go through the manual permission prompt. + it.each(["Bash", "Edit", "Write"])( + "still prompts for %s in default mode", + async (toolName) => { + const context = createContext(toolName, { + session: { + permissionMode: "default", + settingsManager: { + getRepoRoot: vi.fn().mockReturnValue("/repo"), + }, + }, + }); + + await canUseTool(context); + + expect(context.client.requestPermission).toHaveBeenCalled(); + }, + ); +}); diff --git a/packages/agent/src/adapters/claude/tools.ts b/packages/agent/src/adapters/claude/tools.ts index 9074737ae1..eaccc7d218 100644 --- a/packages/agent/src/adapters/claude/tools.ts +++ b/packages/agent/src/adapters/claude/tools.ts @@ -43,7 +43,13 @@ const BASE_ALLOWED_TOOLS = [ ]; const AUTO_ALLOWED_TOOLS: Record> = { - auto: new Set(BASE_ALLOWED_TOOLS), + // Auto mode is hands-off: it auto-approves file edits and shell commands on + // top of the base read/search/web/agent tools. Without WRITE_TOOLS and + // BASH_TOOLS here, Edit/Write/Bash fall through to a manual prompt on every + // call, which contradicts what the mode advertises. MCP tools are still gated + // separately (do_not_use is denied, needs_approval still prompts) in + // canUseTool, so auto stays narrower than bypassPermissions. + auto: new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS, ...BASH_TOOLS]), default: new Set(BASE_ALLOWED_TOOLS), acceptEdits: new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]), plan: new Set(BASE_ALLOWED_TOOLS), diff --git a/packages/agent/src/execution-mode.ts b/packages/agent/src/execution-mode.ts index 99f6799183..f19e85e589 100644 --- a/packages/agent/src/execution-mode.ts +++ b/packages/agent/src/execution-mode.ts @@ -34,7 +34,7 @@ if (ALLOW_BYPASS) { { id: "auto", name: "Auto Mode", - description: "Use a model classifier to approve/deny permission prompts", + description: "Auto-approve file edits and shell commands", }, ); } diff --git a/packages/core/src/sessions/executionModes.ts b/packages/core/src/sessions/executionModes.ts index 8d471d44f1..2c716ced6d 100644 --- a/packages/core/src/sessions/executionModes.ts +++ b/packages/core/src/sessions/executionModes.ts @@ -28,7 +28,7 @@ const availableModes: ModeInfo[] = [ { id: "auto", name: "Auto Mode", - description: "Use a model classifier to approve/deny permission prompts", + description: "Auto-approve file edits and shell commands", }, ];