From 08abe4542dc142779550ec3b5f9f5139756052b0 Mon Sep 17 00:00:00 2001 From: Coco Sheng Date: Wed, 13 May 2026 14:28:30 -0400 Subject: [PATCH] fix(cli): auto-approve shell redirections in AUTO_EDIT mode (#27003) --- .../cli/src/ui/hooks/useGeminiStream.test.tsx | 39 +++++++++++++++++++ packages/cli/src/ui/hooks/useGeminiStream.ts | 19 +++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx index a5e5ea4706..b666956a44 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx +++ b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx @@ -49,6 +49,7 @@ import { debugLogger, coreEvents, CoreEvent, + SHELL_TOOL_NAME, MCPDiscoveryState, GeminiCliOperation, getPlanModeExitMessage, @@ -2449,6 +2450,44 @@ describe('useGeminiStream', () => { ); }); + it('should auto-approve shell commands with redirection when switching to AUTO_EDIT mode', async () => { + const shellCall = createMockToolCall( + SHELL_TOOL_NAME, + 'call-shell', + 'info', + ); + shellCall.request.args = { command: 'ls > files.txt' }; + + const { result } = await renderTestHook([shellCall]); + + await act(async () => { + await result.current.handleApprovalModeChange(ApprovalMode.AUTO_EDIT); + }); + + // Shell command with redirection should be auto-approved + expect(mockMessageBus.publish).toHaveBeenCalledWith( + expect.objectContaining({ correlationId: 'corr-call-shell' }), + ); + }); + + it('should NOT auto-approve shell commands without redirection when switching to AUTO_EDIT mode', async () => { + const shellCall = createMockToolCall( + SHELL_TOOL_NAME, + 'call-shell', + 'info', + ); + shellCall.request.args = { command: 'ls -la' }; + + const { result } = await renderTestHook([shellCall]); + + await act(async () => { + await result.current.handleApprovalModeChange(ApprovalMode.AUTO_EDIT); + }); + + // Regular shell command should NOT be auto-approved + expect(mockMessageBus.publish).not.toHaveBeenCalled(); + }); + it('should not auto-approve any tools when switching to REQUIRE_CONFIRMATION mode', async () => { const awaitingApprovalToolCalls: TrackedToolCall[] = [ createMockToolCall('replace', 'call1', 'edit'), diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts index 828af9b276..6c493e3416 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.ts +++ b/packages/cli/src/ui/hooks/useGeminiStream.ts @@ -26,6 +26,8 @@ import { debugLogger, runInDevTraceSpan, EDIT_TOOL_NAMES, + SHELL_TOOL_NAME, + hasRedirection, processRestorableToolCalls, recordToolCallInteractions, ToolErrorType, @@ -1820,10 +1822,21 @@ export const useGeminiStream = ( ); // For AUTO_EDIT mode, only approve edit tools (replace, write_file) + // or shell commands with redirection (which act as edits). if (newApprovalMode === ApprovalMode.AUTO_EDIT) { - awaitingApprovalCalls = awaitingApprovalCalls.filter((call) => - EDIT_TOOL_NAMES.has(call.request.name), - ); + awaitingApprovalCalls = awaitingApprovalCalls.filter((call) => { + if (EDIT_TOOL_NAMES.has(call.request.name)) { + return true; + } + + if (call.request.name === SHELL_TOOL_NAME) { + const command = (call.request.args as { command?: string }) + .command; + return command && hasRedirection(command); + } + + return false; + }); } // Process pending tool calls sequentially to reduce UI chaos