mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Emit sandbox outcome telemetry event (#25955)
## Summary Adds a dedicated `codex.sandbox_outcome` telemetry event so we can query sandbox edge outcomes without threading sandbox metadata through tool-result output types. This is meant to make sandbox failures and approved escalation retries visible in OTEL while keeping the existing `codex.tool_result` event shape focused on tool completion data. ## What changed - Adds `SessionTelemetry::sandbox_outcome(...)`, which emits `codex.sandbox_outcome` as both a log and trace event. - Records the tool name, call id, sandbox outcome, initial attempt duration, and escalated attempt duration when a retry runs. - Emits `denied` when the sandbox blocks execution and no retry is run. - Emits `timed_out` and `signal` when those sandbox errors surface from tool execution. - Emits `escalated` when the initial sandboxed attempt fails and the approved unsandboxed retry succeeds. - Adds OTEL coverage for the new event payload, including timing fields. ## Validation - `RUST_MIN_STACK=8388608 just test -p codex-core sandbox_outcome_event_records_outcome handle_sandbox_error_user_approves_retry_records_tool_decision` - `just test -p codex-otel otel_export_routing_policy_routes_tool_result_log_and_trace_events runtime_metrics_summary_collects_tool_api_and_streaming_metrics` - `just fix -p codex-core` - `just fix -p codex-otel`
This commit is contained in:
committed by
GitHub
Unverified
parent
4be1a168fc
commit
ecae412740
@@ -39,6 +39,7 @@ use codex_protocol::protocol::NetworkPolicyRuleAction;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
use codex_sandboxing::SandboxManager;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use std::time::Instant;
|
||||
|
||||
pub(crate) struct ToolOrchestrator {
|
||||
sandbox: SandboxManager,
|
||||
@@ -256,6 +257,7 @@ impl ToolOrchestrator {
|
||||
network_denial_cancellation_token: None,
|
||||
};
|
||||
|
||||
let initial_attempt_start = Instant::now();
|
||||
let (first_result, first_deferred_network_approval) = Self::run_attempt(
|
||||
tool,
|
||||
req,
|
||||
@@ -264,6 +266,7 @@ impl ToolOrchestrator {
|
||||
managed_network_active,
|
||||
)
|
||||
.await;
|
||||
let initial_duration = initial_attempt_start.elapsed();
|
||||
match first_result {
|
||||
Ok(out) => {
|
||||
// We have a successful initial result
|
||||
@@ -284,12 +287,26 @@ impl ToolOrchestrator {
|
||||
None
|
||||
};
|
||||
if network_policy_decision.is_some() && network_approval_context.is_none() {
|
||||
otel.sandbox_outcome(
|
||||
&otel_tn,
|
||||
otel_ci,
|
||||
"denied",
|
||||
initial_duration,
|
||||
/*escalated_duration*/ None,
|
||||
);
|
||||
return Err(ToolError::Codex(CodexErr::Sandbox(SandboxErr::Denied {
|
||||
output,
|
||||
network_policy_decision,
|
||||
})));
|
||||
}
|
||||
if !tool.escalate_on_failure() {
|
||||
otel.sandbox_outcome(
|
||||
&otel_tn,
|
||||
otel_ci,
|
||||
"denied",
|
||||
initial_duration,
|
||||
/*escalated_duration*/ None,
|
||||
);
|
||||
return Err(ToolError::Codex(CodexErr::Sandbox(SandboxErr::Denied {
|
||||
output,
|
||||
network_policy_decision,
|
||||
@@ -312,6 +329,13 @@ impl ToolOrchestrator {
|
||||
ExecApprovalRequirement::NeedsApproval { .. }
|
||||
);
|
||||
if !allow_on_request_network_prompt {
|
||||
otel.sandbox_outcome(
|
||||
&otel_tn,
|
||||
otel_ci,
|
||||
"denied",
|
||||
initial_duration,
|
||||
/*escalated_duration*/ None,
|
||||
);
|
||||
return Err(ToolError::Codex(CodexErr::Sandbox(SandboxErr::Denied {
|
||||
output,
|
||||
network_policy_decision,
|
||||
@@ -319,6 +343,13 @@ impl ToolOrchestrator {
|
||||
}
|
||||
}
|
||||
if !unsandboxed_allowed && network_approval_context.is_none() {
|
||||
otel.sandbox_outcome(
|
||||
&otel_tn,
|
||||
otel_ci,
|
||||
"denied",
|
||||
initial_duration,
|
||||
/*escalated_duration*/ None,
|
||||
);
|
||||
return Err(ToolError::Codex(CodexErr::Sandbox(SandboxErr::Denied {
|
||||
output,
|
||||
network_policy_decision,
|
||||
@@ -400,15 +431,51 @@ impl ToolOrchestrator {
|
||||
};
|
||||
|
||||
// Second attempt.
|
||||
let escalated_attempt_start = Instant::now();
|
||||
let (retry_result, retry_deferred_network_approval) =
|
||||
Self::run_attempt(tool, req, tool_ctx, &retry_attempt, managed_network_active)
|
||||
.await;
|
||||
retry_result.map(|output| OrchestratorRunResult {
|
||||
output,
|
||||
deferred_network_approval: retry_deferred_network_approval,
|
||||
})
|
||||
let escalated_duration = escalated_attempt_start.elapsed();
|
||||
match retry_result {
|
||||
Ok(output) => {
|
||||
otel.sandbox_outcome(
|
||||
&otel_tn,
|
||||
otel_ci,
|
||||
"escalated",
|
||||
initial_duration,
|
||||
Some(escalated_duration),
|
||||
);
|
||||
Ok(OrchestratorRunResult {
|
||||
output,
|
||||
deferred_network_approval: retry_deferred_network_approval,
|
||||
})
|
||||
}
|
||||
Err(err) => {
|
||||
if let Some(outcome) = sandbox_outcome_from_tool_error(&err) {
|
||||
otel.sandbox_outcome(
|
||||
&otel_tn,
|
||||
otel_ci,
|
||||
outcome,
|
||||
initial_duration,
|
||||
Some(escalated_duration),
|
||||
);
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if let Some(outcome) = sandbox_outcome_from_tool_error(&err) {
|
||||
otel.sandbox_outcome(
|
||||
&otel_tn,
|
||||
otel_ci,
|
||||
outcome,
|
||||
initial_duration,
|
||||
/*escalated_duration*/ None,
|
||||
);
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,6 +576,15 @@ impl ToolOrchestrator {
|
||||
}
|
||||
}
|
||||
|
||||
fn sandbox_outcome_from_tool_error(err: &ToolError) -> Option<&'static str> {
|
||||
match err {
|
||||
ToolError::Codex(CodexErr::Sandbox(SandboxErr::Denied { .. })) => Some("denied"),
|
||||
ToolError::Codex(CodexErr::Sandbox(SandboxErr::Timeout { .. })) => Some("timed_out"),
|
||||
ToolError::Codex(CodexErr::Sandbox(SandboxErr::Signal(_))) => Some("signal"),
|
||||
ToolError::Rejected(_) | ToolError::Codex(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_denial_reason_from_output(_output: &ExecToolCallOutput) -> String {
|
||||
// Keep approval reason terse and stable for UX/tests, but accept the
|
||||
// output so we can evolve heuristics later without touching call sites.
|
||||
|
||||
Reference in New Issue
Block a user