From e2e3f4490e80e9b7e18ca190aa92eb38f80d1ede Mon Sep 17 00:00:00 2001 From: jif-oai Date: Fri, 9 Jan 2026 13:10:31 +0000 Subject: [PATCH] chore: add approval metric (#8970) --- codex-rs/core/src/tools/runtimes/apply_patch.rs | 17 +++++++++++------ codex-rs/core/src/tools/runtimes/shell.rs | 2 +- .../core/src/tools/runtimes/unified_exec.rs | 2 +- codex-rs/core/src/tools/sandboxing.rs | 11 +++++++++++ codex-rs/protocol/src/protocol.rs | 14 ++++++++++++++ 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/codex-rs/core/src/tools/runtimes/apply_patch.rs b/codex-rs/core/src/tools/runtimes/apply_patch.rs index 7b9d0dccc..ff22a9cbc 100644 --- a/codex-rs/core/src/tools/runtimes/apply_patch.rs +++ b/codex-rs/core/src/tools/runtimes/apply_patch.rs @@ -111,12 +111,17 @@ impl Approvable for ApplyPatchRuntime { return rx_approve.await.unwrap_or_default(); } - with_cached_approval(&session.services, approval_keys, || async move { - let rx_approve = session - .request_patch_approval(turn, call_id, changes, None, None) - .await; - rx_approve.await.unwrap_or_default() - }) + with_cached_approval( + &session.services, + "apply_patch", + approval_keys, + || async move { + let rx_approve = session + .request_patch_approval(turn, call_id, changes, None, None) + .await; + rx_approve.await.unwrap_or_default() + }, + ) .await }) } diff --git a/codex-rs/core/src/tools/runtimes/shell.rs b/codex-rs/core/src/tools/runtimes/shell.rs index 49052bc06..fc862a9ae 100644 --- a/codex-rs/core/src/tools/runtimes/shell.rs +++ b/codex-rs/core/src/tools/runtimes/shell.rs @@ -98,7 +98,7 @@ impl Approvable for ShellRuntime { let turn = ctx.turn; let call_id = ctx.call_id.to_string(); Box::pin(async move { - with_cached_approval(&session.services, keys, move || async move { + with_cached_approval(&session.services, "shell", keys, move || async move { session .request_command_approval( turn, diff --git a/codex-rs/core/src/tools/runtimes/unified_exec.rs b/codex-rs/core/src/tools/runtimes/unified_exec.rs index 47e9e5ca1..58ca66fcb 100644 --- a/codex-rs/core/src/tools/runtimes/unified_exec.rs +++ b/codex-rs/core/src/tools/runtimes/unified_exec.rs @@ -116,7 +116,7 @@ impl Approvable for UnifiedExecRuntime<'_> { .clone() .or_else(|| req.justification.clone()); Box::pin(async move { - with_cached_approval(&session.services, keys, || async move { + with_cached_approval(&session.services, "unified_exec", keys, || async move { session .request_command_approval( turn, diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index 82af60e3d..eefce38bc 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -57,6 +57,8 @@ impl ApprovalStore { /// so future requests touching any subset can also skip prompting. pub(crate) async fn with_cached_approval( services: &SessionServices, + // Name of the tool, used for metrics collection. + tool_name: &str, keys: Vec, fetch: F, ) -> ReviewDecision @@ -82,6 +84,15 @@ where let decision = fetch().await; + services.otel_manager.counter( + "codex.approval.requested", + 1, + &[ + ("tool", tool_name), + ("approved", decision.to_opaque_string()), + ], + ); + if matches!(decision, ReviewDecision::ApprovedForSession) { let mut store = services.tool_approvals.lock().await; for key in keys { diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index e3748bafc..c36084861 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -1878,6 +1878,20 @@ pub enum ReviewDecision { Abort, } +impl ReviewDecision { + /// Returns an opaque version of the decision without PII. We can't use an ignored flag + /// on `serde` because the serialization is required by some surfaces. + pub fn to_opaque_string(&self) -> &'static str { + match self { + ReviewDecision::Approved => "approved", + ReviewDecision::ApprovedExecpolicyAmendment { .. } => "approved_with_amendment", + ReviewDecision::ApprovedForSession => "approved_for_session", + ReviewDecision::Denied => "denied", + ReviewDecision::Abort => "abort", + } + } +} + #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "snake_case")] #[ts(tag = "type")]