[codex-analytics] Emit structured compaction codex errors (#27082)

## Summary
- replace raw compaction `error` analytics with `codex_error_kind` and
`codex_error_http_status_code`
- derive compaction error telemetry from `CodexErr` using the same
`CodexErrKind` mapping and HTTP status helper used by turn events
- remove the pre-compact hook stop reason from the internal compaction
outcome now that it is no longer emitted as raw analytics text

## Why
Compaction `error` was a raw `CodexErr::to_string()` value, which can
carry free-form provider or user-derived text. Structured Codex error
fields preserve useful low-cardinality telemetry without sending the raw
string.

## Validation
- `just fmt`
- `just test -p codex-analytics`
- `just test -p codex-core
compact::tests::build_token_limited_compacted_history_appends_summary_message`

Attempted `just test -p codex-core`; the changed crate compiled, but the
full target failed in unrelated environment-dependent tests such as
missing helper binaries and shell snapshot timeouts.
This commit is contained in:
rhan-oai
2026-06-10 23:07:06 -07:00
committed by GitHub
Unverified
parent b5a718ef67
commit 8d9f33c87c
8 changed files with 52 additions and 35 deletions
+11 -9
View File
@@ -143,17 +143,17 @@ async fn run_compact_task_inner(
let pre_compact_outcome = run_pre_compact_hooks(&sess, &turn_context, trigger).await;
match pre_compact_outcome {
PreCompactHookOutcome::Continue => {}
PreCompactHookOutcome::Stopped { reason } => {
let error = reason.unwrap_or_else(|| "PreCompact hook stopped execution".to_string());
PreCompactHookOutcome::Stopped => {
let error = CodexErr::TurnAborted;
attempt
.track(
sess.as_ref(),
CompactionStatus::Interrupted,
Some(error),
Some(&error),
CompactionAnalyticsDetails::default(),
)
.await;
return Err(CodexErr::TurnAborted);
return Err(error);
}
}
let result = run_compact_task_inner_impl(
@@ -165,7 +165,7 @@ async fn run_compact_task_inner(
)
.await;
let status = compaction_status_from_result(&result);
let error = result.as_ref().err().map(ToString::to_string);
let codex_error = result.as_ref().err();
if result.is_ok() {
let post_compact_outcome = run_post_compact_hooks(&sess, &turn_context, trigger).await;
if let PostCompactHookOutcome::Stopped = post_compact_outcome {
@@ -173,7 +173,7 @@ async fn run_compact_task_inner(
.track(
sess.as_ref(),
status,
error,
codex_error,
CompactionAnalyticsDetails::default(),
)
.await;
@@ -184,7 +184,7 @@ async fn run_compact_task_inner(
.track(
sess.as_ref(),
status,
error,
codex_error,
CompactionAnalyticsDetails::default(),
)
.await;
@@ -373,7 +373,7 @@ impl CompactionAnalyticsAttempt {
self,
sess: &Session,
status: CompactionStatus,
error: Option<String>,
codex_error: Option<&CodexErr>,
details: CompactionAnalyticsDetails,
) {
let CompactionAnalyticsDetails {
@@ -396,7 +396,9 @@ impl CompactionAnalyticsAttempt {
phase: self.phase,
strategy: CompactionStrategy::Memento,
status,
error,
codex_error_kind: codex_error.map(Into::into),
codex_error_http_status_code: codex_error
.and_then(CodexErr::http_status_code_value),
active_context_tokens_before,
active_context_tokens_after,
retained_image_count,
+7 -7
View File
@@ -113,17 +113,17 @@ async fn run_remote_compact_task_inner(
let pre_compact_outcome = run_pre_compact_hooks(sess, turn_context, trigger).await;
match pre_compact_outcome {
PreCompactHookOutcome::Continue => {}
PreCompactHookOutcome::Stopped { reason } => {
let error = reason.unwrap_or_else(|| "PreCompact hook stopped execution".to_string());
PreCompactHookOutcome::Stopped => {
let error = CodexErr::TurnAborted;
attempt
.track(
sess.as_ref(),
codex_analytics::CompactionStatus::Interrupted,
Some(error),
Some(&error),
analytics_details,
)
.await;
return Err(CodexErr::TurnAborted);
return Err(error);
}
}
let result = run_remote_compact_task_inner_impl(
@@ -135,18 +135,18 @@ async fn run_remote_compact_task_inner(
)
.await;
let status = compaction_status_from_result(&result);
let error = result.as_ref().err().map(ToString::to_string);
let codex_error = result.as_ref().err();
if result.is_ok() {
let post_compact_outcome = run_post_compact_hooks(sess, turn_context, trigger).await;
if let PostCompactHookOutcome::Stopped = post_compact_outcome {
attempt
.track(sess.as_ref(), status, error, analytics_details)
.track(sess.as_ref(), status, codex_error, analytics_details)
.await;
return Err(CodexErr::TurnAborted);
}
}
attempt
.track(sess.as_ref(), status, error.clone(), analytics_details)
.track(sess.as_ref(), status, codex_error, analytics_details)
.await;
if let Err(err) = result {
sess.track_turn_codex_error(turn_context, &err);
+7 -7
View File
@@ -127,17 +127,17 @@ async fn run_remote_compact_task_inner(
let pre_compact_outcome = run_pre_compact_hooks(sess, turn_context, trigger).await;
match pre_compact_outcome {
PreCompactHookOutcome::Continue => {}
PreCompactHookOutcome::Stopped { reason } => {
let error = reason.unwrap_or_else(|| "PreCompact hook stopped execution".to_string());
PreCompactHookOutcome::Stopped => {
let error = CodexErr::TurnAborted;
attempt
.track(
sess.as_ref(),
codex_analytics::CompactionStatus::Interrupted,
Some(error),
Some(&error),
analytics_details,
)
.await;
return Err(CodexErr::TurnAborted);
return Err(error);
}
}
let result = run_remote_compact_task_inner_impl(
@@ -150,18 +150,18 @@ async fn run_remote_compact_task_inner(
)
.await;
let status = compaction_status_from_result(&result);
let error = result.as_ref().err().map(ToString::to_string);
let codex_error = result.as_ref().err();
if result.is_ok() {
let post_compact_outcome = run_post_compact_hooks(sess, turn_context, trigger).await;
if let PostCompactHookOutcome::Stopped = post_compact_outcome {
attempt
.track(sess.as_ref(), status, error, analytics_details)
.track(sess.as_ref(), status, codex_error, analytics_details)
.await;
return Err(CodexErr::TurnAborted);
}
}
attempt
.track(sess.as_ref(), status, error.clone(), analytics_details)
.track(sess.as_ref(), status, codex_error, analytics_details)
.await;
if let Err(err) = result {
sess.track_turn_codex_error(turn_context, &err);
+2 -4
View File
@@ -385,9 +385,7 @@ pub(crate) async fn run_pre_compact_hooks(
let outcome = sess.hooks().run_pre_compact(request).await;
emit_hook_completed_events(sess, turn_context, outcome.hook_events).await;
if outcome.should_stop {
PreCompactHookOutcome::Stopped {
reason: outcome.stop_reason,
}
PreCompactHookOutcome::Stopped
} else {
PreCompactHookOutcome::Continue
}
@@ -395,7 +393,7 @@ pub(crate) async fn run_pre_compact_hooks(
pub(crate) enum PreCompactHookOutcome {
Continue,
Stopped { reason: Option<String> },
Stopped,
}
pub(crate) enum PostCompactHookOutcome {