Route permission requests through shared Guardian approvals (#38701)

## What changed

- Represent `request_permissions` calls as shared approval actions and convert them into Guardian permission requests through the common approval path.
- Preserve turn cancellation while an automatic permission review is pending.
- Cover Guardian allow and deny decisions, cancellation without a user-approval fallback, and clean follow-up turns.

GitOrigin-RevId: 95d0df0288e232f32b162756aa7d64a30df7efa5
This commit is contained in:
Dylan Hurd
2026-08-15 05:09:19 +00:00
committed by copyberry
parent 4861236f06
commit 53f3fa7496
3 changed files with 341 additions and 26 deletions
+19 -21
View File
@@ -263,7 +263,6 @@ pub(crate) struct PreviousTurnSettings {
}
use crate::exec_policy::ExecPolicyUpdateError;
use crate::guardian::GuardianReviewOptions;
use crate::guardian::GuardianReviewSessionManager;
use crate::mcp::McpManager;
use crate::mcp::McpThreadIdentity;
@@ -281,11 +280,13 @@ use crate::stream_events_utils::HandleOutputCtx;
#[cfg(test)]
use crate::stream_events_utils::handle_output_item_done;
use crate::tasks::ReviewTask;
use crate::tools::ApprovalContext;
use crate::tools::network_approval::NetworkApprovalService;
use crate::tools::network_approval::build_blocked_request_observer;
use crate::tools::network_approval::build_network_policy_decider;
#[cfg(test)]
use crate::tools::parallel::ToolCallRuntime;
use crate::tools::sandboxing::ApprovalAction;
use crate::tools::sandboxing::ApprovalStore;
use crate::turn_timing::TurnTimingState;
use crate::turn_timing::record_turn_ttfm_metric;
@@ -347,6 +348,7 @@ use codex_protocol::turn_input::TurnInputRequest;
use codex_protocol::turn_input::TurnInputSubmission;
use codex_protocol::user_input::UserInput;
use codex_skills_extension::HostSkillsService;
use codex_tools::ToolName;
use codex_tools::UnifiedExecShellMode;
use codex_utils_absolute_path::AbsolutePathBuf;
#[cfg(test)]
@@ -2518,33 +2520,29 @@ impl Session {
let active = self.active_turn.lock().await;
active.as_ref().map(|active| Arc::clone(&active.turn_state))
};
let review_id = crate::guardian::new_guardian_review_id();
let session = Arc::clone(self);
let request = crate::guardian::GuardianApprovalRequest::RequestPermissions {
id: call_id,
let action = ApprovalAction::RequestPermissions {
id: call_id.clone(),
turn_id: turn_context.sub_id.clone(),
reason: args.reason,
permissions: requested_permissions.clone(),
};
let review_rx = crate::guardian::spawn_approval_request_review(
session,
review_context.clone(),
review_id,
request,
/*retry_reason*/ None,
GuardianReviewOptions {
plugin_attribution_override: None,
approval_request_source:
codex_analytics::GuardianApprovalRequestSource::MainTurn,
external_cancel: Some(cancellation_token.clone()),
},
);
let approval_context = ApprovalContext {
review_context: review_context.clone(),
call_id,
tool_name: ToolName::plain("request_permissions"),
strict_auto_review: false,
approval_reason: None,
retry_reason: None,
network_approval_context: None,
};
let decision = tokio::select! {
biased;
_ = cancellation_token.cancelled() => return None,
decision = review_rx => decision.unwrap_or_else(|_| {
ReviewDecision::denied("automatic approval review could not complete")
}),
decision = self.request_guardian_approval(
action,
&approval_context,
Some(cancellation_token.clone()),
) => decision,
};
let response = match decision {
ReviewDecision::Approved | ReviewDecision::ApprovedExecpolicyAmendment { .. } => {