[codex-analytics] add grouped session id to runtime events (#24655)

## Why
- Runtime analytics events report `thread_id`, which identifies the
individual thread emitting an event
- They don't report `session_id`, which identifies the shared session
for a root thread and its subagent threads
- Emitting both identifiers allows analytics to group related activity

## What Changed
- Adds `session_id` to relevant analytics events (thread_initalized,
turn, turn_steer, compaction, guardian_review)
- Tracks each thread's session ID in the analytics reducer so subsequent
thread scoped events emit the same value
- Carries the shared session ID through subagent initialization

## Verification
- `just test -p codex-analytics` validates event payloads and subagent
session grouping.
- Focused `codex-app-server` tests validate session IDs for thread,
turn, and steer events.
- Focused `codex-core` tests validate root and subagent session ID
propagation.
This commit is contained in:
marksteinbrick-oai
2026-05-26 16:38:46 -07:00
committed by GitHub
parent dc4e54d061
commit 487521733b
13 changed files with 87 additions and 5 deletions

View File

@@ -168,11 +168,13 @@ pub(crate) fn thread_initialized_event(payload: &Value) -> Result<&Value> {
pub(crate) fn assert_basic_thread_initialized_event(
event: &Value,
thread_id: &str,
session_id: &str,
expected_model: &str,
initialization_mode: &str,
expected_thread_source: &str,
) {
assert_eq!(event["event_params"]["thread_id"], thread_id);
assert_eq!(event["event_params"]["session_id"], session_id);
assert_eq!(
event["event_params"]["app_server_client"]["product_client_id"],
DEFAULT_CLIENT_NAME

View File

@@ -405,7 +405,14 @@ async fn thread_fork_tracks_thread_initialized_analytics() -> Result<()> {
let payload = wait_for_analytics_payload(&server, DEFAULT_READ_TIMEOUT).await?;
let event = thread_initialized_event(&payload)?;
assert_basic_thread_initialized_event(event, &thread.id, "mock-model", "forked", "user");
assert_basic_thread_initialized_event(
event,
&thread.id,
&thread.session_id,
"mock-model",
"forked",
"user",
);
Ok(())
}

View File

@@ -422,7 +422,14 @@ async fn thread_resume_tracks_thread_initialized_analytics() -> Result<()> {
let payload = wait_for_analytics_payload(&server, DEFAULT_READ_TIMEOUT).await?;
let event = thread_initialized_event(&payload)?;
assert_basic_thread_initialized_event(event, &thread.id, "gpt-5.3-codex", "resumed", "user");
assert_basic_thread_initialized_event(
event,
&thread.id,
&thread.session_id,
"gpt-5.3-codex",
"resumed",
"user",
);
assert_eq!(event["event_params"]["thread_source"], "user");
Ok(())
}

View File

@@ -439,7 +439,14 @@ async fn thread_start_tracks_thread_initialized_analytics() -> Result<()> {
let payload = wait_for_analytics_payload(&server, DEFAULT_READ_TIMEOUT).await?;
assert_eq!(payload["events"].as_array().expect("events array").len(), 1);
let event = thread_initialized_event(&payload)?;
assert_basic_thread_initialized_event(event, &thread.id, "mock-model", "new", "user");
assert_basic_thread_initialized_event(
event,
&thread.id,
&thread.session_id,
"mock-model",
"new",
"user",
);
Ok(())
}

View File

@@ -862,6 +862,7 @@ async fn turn_start_tracks_turn_event_analytics() -> Result<()> {
let event = wait_for_analytics_event(&server, DEFAULT_READ_TIMEOUT, "codex_turn_event").await?;
assert_eq!(event["event_params"]["thread_id"], thread.id);
assert_eq!(event["event_params"]["session_id"], thread.session_id);
assert_eq!(event["event_params"]["turn_id"], turn.id);
assert_eq!(
event["event_params"]["app_server_client"]["product_client_id"],

View File

@@ -306,6 +306,7 @@ async fn turn_steer_returns_active_turn_id() -> Result<()> {
let event =
wait_for_analytics_event(&server, DEFAULT_READ_TIMEOUT, "codex_turn_steer_event").await?;
assert_eq!(event["event_params"]["thread_id"], thread.id);
assert_eq!(event["event_params"]["session_id"], thread.session_id);
assert_eq!(event["event_params"]["result"], "accepted");
assert_eq!(event["event_params"]["num_input_images"], 0);
assert_eq!(event["event_params"]["expected_turn_id"], turn.id);