Skip Guardian scoring in User approval mode (#42256)

## What changed

- Skip Guardian prewarming and asynchronous scoring when `approvalsReviewer` is
  `"user"`, including when the reviewer changes during an active turn.
- Automatically accept ordinary `node_repl.js` execution confirmations in this
  mode while continuing to surface sensitive-action checks and requests for user
  input.
- Cover transitions between User approval, automatic review, and Full Access in
  the app-server integration tests.

GitOrigin-RevId: 599c8ebb672728b9bf3d64c34377ddec230325f8
This commit is contained in:
jif
2026-09-02 10:32:22 +00:00
committed by copyberry
parent bdfd769640
commit a0dcfe2ada
7 changed files with 182 additions and 54 deletions
+14
View File
@@ -733,6 +733,20 @@ impl CodexThread {
self.session.thread_config_snapshot().await
}
/// Returns the active turn's reviewer, including live updates, or the thread default.
pub async fn approvals_reviewer_for_turn(&self, turn_id: &str) -> ApprovalsReviewer {
if let Some((turn, settings, _)) = self
.session
.active_turn_context_and_strict_auto_review()
.await
&& turn.sub_id == turn_id
{
settings.approvals_reviewer()
} else {
self.config_snapshot().await.approvals_reviewer
}
}
/// Returns thread-owned settings suitable for rollout persistence and resume.
pub async fn thread_settings_snapshot(&self) -> ThreadSettingsSnapshot {
self.session.thread_settings_snapshot().await
+27 -12
View File
@@ -726,19 +726,34 @@ async fn review_guardian_mcp_elicitation(
};
let step_settings = turn_context.current_settings.load_full();
// User approval skips ordinary CUA checks, not separate sensitive requests.
let user_cua_execution = step_settings.approvals_reviewer() == ApprovalsReviewer::User
&& is_node_repl_backed_server(&request.server_name)
&& request.elicitation.meta().is_some_and(|meta| {
metadata_str(meta, MCP_ELICITATION_TOOL_NAME_KEY) == Some("js")
&& metadata_str(meta, MCP_ELICITATION_CONNECTOR_ID_KEY) == Some("node_repl")
&& metadata_str(meta, MCP_ELICITATION_APPROVAL_KIND_KEY)
== Some(MCP_ELICITATION_APPROVAL_KIND_MCP_TOOL_CALL)
&& meta.get(MCP_ELICITATION_SENSITIVE_ACTION_KEY) != Some(&Value::Bool(true))
&& meta.get("codex_requires_user_input") != Some(&Value::Bool(true))
});
// Full Access skips inference, not the active-turn and cancellation checks.
if turn_context.environments.has_full_access(
turn_context.approval_policy(),
&turn_context
.config
.permissions
.effective_permission_profile(),
) && matches!(
&request.elicitation,
Elicitation::Mcp(rmcp::model::ElicitRequestParams::FormElicitationParams {
requested_schema, ..
}) if requested_schema.properties.is_empty()
) {
if (user_cua_execution
|| turn_context.environments.has_full_access(
turn_context.approval_policy(),
&turn_context
.config
.permissions
.effective_permission_profile(),
))
&& matches!(
&request.elicitation,
Elicitation::Mcp(rmcp::model::ElicitRequestParams::FormElicitationParams {
requested_schema, ..
}) if requested_schema.properties.is_empty()
)
{
let decision = if cancellation_token.is_cancelled() {
ReviewDecision::Abort
} else {