[codex-analytics] Track CodexErr details in turn analytics (#25707)

## Summary
- add analytics-only `CodexErr` telemetry to `codex_turn_event` while
leaving existing `turn_error` unchanged
- record terminal `CodexErr` facts from core immediately before the
existing turn error event is sent
- emit source-truth `codex_error_*` fields for downstream analytics,
including the raw `CodexErr::InvalidRequest(String)` message as
`codex_error_subreason`

## Validation
- `just test -p codex-analytics`
- attempted `just test -p codex-core`, but the local run timed out
across unrelated integration suites in this environment and is not being
used as validation
This commit is contained in:
rhan-oai
2026-06-02 11:40:35 -07:00
committed by GitHub
parent 503ec190a8
commit 8e4b92d294
11 changed files with 249 additions and 1 deletions
+2
View File
@@ -240,6 +240,7 @@ async fn run_compact_task_inner_impl(
continue;
}
sess.set_total_tokens_full(turn_context.as_ref()).await;
sess.track_turn_codex_error(turn_context.as_ref(), &e);
let event = EventMsg::Error(e.to_error_event(/*message_prefix*/ None));
sess.send_event(&turn_context, event).await;
return Err(e);
@@ -257,6 +258,7 @@ async fn run_compact_task_inner_impl(
tokio::time::sleep(delay).await;
continue;
} else {
sess.track_turn_codex_error(turn_context.as_ref(), &e);
let event = EventMsg::Error(e.to_error_event(/*message_prefix*/ None));
sess.send_event(&turn_context, event).await;
return Err(e);
+1
View File
@@ -138,6 +138,7 @@ async fn run_remote_compact_task_inner(
}
attempt.track(sess.as_ref(), status, error.clone()).await;
if let Err(err) = result {
sess.track_turn_codex_error(turn_context, &err);
let event = EventMsg::Error(
err.to_error_event(Some("Error running remote compact task".to_string())),
);
+1
View File
@@ -155,6 +155,7 @@ async fn run_remote_compact_task_inner(
}
attempt.track(sess.as_ref(), status, error.clone()).await;
if let Err(err) = result {
sess.track_turn_codex_error(turn_context, &err);
let event = EventMsg::Error(
err.to_error_event(Some("Error running remote compact task".to_string())),
);
+12
View File
@@ -45,6 +45,7 @@ use chrono::Local;
use chrono::Utc;
use codex_analytics::AnalyticsEventsClient;
use codex_analytics::SubAgentThreadStartedInput;
use codex_analytics::TurnCodexErrorFact;
use codex_app_server_protocol::McpServerElicitationRequest;
use codex_app_server_protocol::McpServerElicitationRequestParams;
use codex_config::types::OAuthCredentialsStoreMode;
@@ -1634,6 +1635,17 @@ impl Session {
)
}
/// Record a terminal CodexErr before the app-server completion notification is reduced.
pub(crate) fn track_turn_codex_error(&self, turn_context: &TurnContext, error: &CodexErr) {
self.services
.analytics_events_client
.track_turn_codex_error(TurnCodexErrorFact::from_codex_err(
self.conversation_id.to_string(),
turn_context.sub_id.clone(),
error,
));
}
/// Persist the event to rollout and send it to clients.
pub(crate) async fn send_event(&self, turn_context: &TurnContext, msg: EventMsg) {
let legacy_source = msg.clone();
+3 -1
View File
@@ -369,7 +369,7 @@ pub(crate) async fn run_turn(
// Aborted turn is reported via a different event.
break;
}
Err(CodexErr::InvalidImageRequest()) => {
Err(codex_error @ CodexErr::InvalidImageRequest()) => {
{
let mut state = sess.state.lock().await;
error_or_panic(
@@ -380,6 +380,7 @@ pub(crate) async fn run_turn(
}
}
sess.track_turn_codex_error(turn_context.as_ref(), &codex_error);
let error = CodexErrorInfo::BadRequest;
sess.emit_turn_error_lifecycle(turn_context.as_ref(), error.clone())
.await;
@@ -405,6 +406,7 @@ pub(crate) async fn run_turn(
{
warn!("failed to usage-limit active goal after usage-limit error: {err}");
}
sess.track_turn_codex_error(turn_context.as_ref(), &e);
let event = EventMsg::Error(e.to_error_event(/*message_prefix*/ None));
sess.send_event(&turn_context, event).await;
// let the user continue the conversation