mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
Add codex_hook_run analytics event (#17996)
# Why
Add product analytics for hook handler executions so we can understand
which hooks are running, where they came from, and whether they
completed, failed, stopped, or blocked work.
# What
- add the new `codex_hook_run` analytics event and payload plumbing in
`codex-rs/analytics`
- emit hook-run analytics from the shared hook completion path in
`codex-rs/core`
- classify hook source from the loaded hook path as `system`, `user`,
`project`, or `unknown`
```
{
"event_type": "codex_hook_run",
"event_params": {
"thread_id": "string",
"turn_id": "string",
"model_slug": "string",
"hook_name": "string, // any HookEventName
"hook_source": "system | user | project | unknown",
"status": "completed | failed | stopped | blocked"
}
}
```
---------
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_analytics::HookRunFact;
|
||||
use codex_analytics::build_track_events_context;
|
||||
use codex_hooks::PostToolUseOutcome;
|
||||
use codex_hooks::PostToolUseRequest;
|
||||
use codex_hooks::PreToolUseOutcome;
|
||||
@@ -316,17 +318,52 @@ async fn emit_hook_started_events(
|
||||
}
|
||||
}
|
||||
|
||||
async fn emit_hook_completed_events(
|
||||
pub(crate) async fn emit_hook_completed_events(
|
||||
sess: &Arc<Session>,
|
||||
turn_context: &Arc<TurnContext>,
|
||||
completed_events: Vec<HookCompletedEvent>,
|
||||
) {
|
||||
for completed in completed_events {
|
||||
track_hook_completed_analytics(sess, turn_context, &completed);
|
||||
sess.send_event(turn_context, EventMsg::HookCompleted(completed))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
fn track_hook_completed_analytics(
|
||||
sess: &Arc<Session>,
|
||||
turn_context: &Arc<TurnContext>,
|
||||
completed: &HookCompletedEvent,
|
||||
) {
|
||||
let (tracking, hook) =
|
||||
hook_run_analytics_payload(sess.conversation_id.to_string(), turn_context, completed);
|
||||
sess.services
|
||||
.analytics_events_client
|
||||
.track_hook_run(tracking, hook);
|
||||
}
|
||||
|
||||
fn hook_run_analytics_payload(
|
||||
thread_id: String,
|
||||
turn_context: &TurnContext,
|
||||
completed: &HookCompletedEvent,
|
||||
) -> (codex_analytics::TrackEventsContext, HookRunFact) {
|
||||
(
|
||||
build_track_events_context(
|
||||
turn_context.model_info.slug.clone(),
|
||||
thread_id,
|
||||
completed
|
||||
.turn_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| turn_context.sub_id.clone()),
|
||||
),
|
||||
HookRunFact {
|
||||
event_name: completed.run.event_name,
|
||||
hook_source: completed.run.source,
|
||||
status: completed.run.status,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn hook_permission_mode(turn_context: &TurnContext) -> String {
|
||||
match turn_context.approval_policy.value() {
|
||||
AskForApproval::Never => "bypassPermissions",
|
||||
@@ -341,9 +378,21 @@ fn hook_permission_mode(turn_context: &TurnContext) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::protocol::HookEventName;
|
||||
use codex_protocol::protocol::HookExecutionMode;
|
||||
use codex_protocol::protocol::HookHandlerType;
|
||||
use codex_protocol::protocol::HookRunStatus;
|
||||
use codex_protocol::protocol::HookScope;
|
||||
use codex_protocol::protocol::HookSource;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::additional_context_messages;
|
||||
use super::hook_run_analytics_payload;
|
||||
use crate::codex::make_session_and_context;
|
||||
use codex_protocol::protocol::HookCompletedEvent;
|
||||
use codex_protocol::protocol::HookRunSummary;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::test_path_buf;
|
||||
|
||||
#[test]
|
||||
fn additional_context_messages_stay_separate_and_ordered() {
|
||||
@@ -378,4 +427,58 @@ mod tests {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hook_run_analytics_payload_uses_completed_turn_id() {
|
||||
let (_session, turn_context) = make_session_and_context().await;
|
||||
let completed = HookCompletedEvent {
|
||||
turn_id: Some("turn-from-hook".to_string()),
|
||||
run: sample_hook_run(HookRunStatus::Blocked, HookSource::Project),
|
||||
};
|
||||
|
||||
let (tracking, hook) =
|
||||
hook_run_analytics_payload("thread-123".to_string(), &turn_context, &completed);
|
||||
|
||||
assert_eq!(tracking.thread_id, "thread-123");
|
||||
assert_eq!(tracking.turn_id, "turn-from-hook");
|
||||
assert_eq!(tracking.model_slug, turn_context.model_info.slug);
|
||||
assert_eq!(hook.event_name, HookEventName::Stop);
|
||||
assert_eq!(hook.hook_source, HookSource::Project);
|
||||
assert_eq!(hook.status, HookRunStatus::Blocked);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hook_run_analytics_payload_falls_back_to_turn_context_id() {
|
||||
let (_session, turn_context) = make_session_and_context().await;
|
||||
let completed = HookCompletedEvent {
|
||||
turn_id: None,
|
||||
run: sample_hook_run(HookRunStatus::Failed, HookSource::Unknown),
|
||||
};
|
||||
|
||||
let (tracking, hook) =
|
||||
hook_run_analytics_payload("thread-123".to_string(), &turn_context, &completed);
|
||||
|
||||
assert_eq!(tracking.turn_id, turn_context.sub_id);
|
||||
assert_eq!(hook.hook_source, HookSource::Unknown);
|
||||
assert_eq!(hook.status, HookRunStatus::Failed);
|
||||
}
|
||||
|
||||
fn sample_hook_run(status: HookRunStatus, source: HookSource) -> HookRunSummary {
|
||||
HookRunSummary {
|
||||
id: "stop:0:/tmp/hooks.json".to_string(),
|
||||
event_name: HookEventName::Stop,
|
||||
handler_type: HookHandlerType::Command,
|
||||
execution_mode: HookExecutionMode::Sync,
|
||||
scope: HookScope::Turn,
|
||||
source_path: test_path_buf("/tmp/hooks.json").abs(),
|
||||
source,
|
||||
display_order: 0,
|
||||
status,
|
||||
status_message: None,
|
||||
started_at: 10,
|
||||
completed_at: Some(37),
|
||||
duration_ms: Some(27),
|
||||
entries: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user