diff --git a/src/agent/loop_.rs b/src/agent/loop_.rs index c6ae4b66b..07fd02fa3 100644 --- a/src/agent/loop_.rs +++ b/src/agent/loop_.rs @@ -2148,6 +2148,8 @@ pub async fn run_tool_call_loop( arguments: tool_args.clone(), }; + // Only prompt interactively on CLI; deny on other channels + // (remote channels cannot provide interactive approval). let decision = if channel_name == "cli" { mgr.prompt_cli(&request) } else if let Some(ctx) = non_cli_approval_context.as_ref() { @@ -2178,13 +2180,26 @@ pub async fn run_tool_call_loop( ) .await } else { + tracing::warn!( + tool = %tool_name, + channel = %channel_name, + "Tool requires approval but channel cannot prompt — denied" + ); ApprovalResponse::No }; mgr.record_decision(&tool_name, &tool_args, decision, channel_name); if decision == ApprovalResponse::No { - let denied = "Denied by user.".to_string(); + let denied = if channel_name == "cli" { + "Denied by user.".to_string() + } else { + format!( + "Tool '{}' requires approval but channel '{}' cannot prompt interactively. \ + Configure auto_approve or set autonomy level to Full to allow.", + tool_name, channel_name + ) + }; runtime_trace::record_event( "tool_call_result", Some(channel_name), diff --git a/src/approval/mod.rs b/src/approval/mod.rs index 3e44327ea..55a355122 100644 --- a/src/approval/mod.rs +++ b/src/approval/mod.rs @@ -636,8 +636,9 @@ impl ApprovalManager { /// Prompt the user on the CLI and return their decision. /// - /// For non-CLI channels, returns `Yes` automatically (interactive - /// approval is only supported on CLI for now). + /// Only valid for the CLI channel. Non-CLI channels should not call + /// this method; the caller in `run_tool_call_loop` denies by default + /// when the channel cannot provide interactive approval. pub fn prompt_cli(&self, request: &ApprovalRequest) -> ApprovalResponse { prompt_cli_interactive(request) }