diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index d53a4c12be..895c0962cd 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -66,7 +66,6 @@ use crate::facts::SkillInvokedInput; use crate::facts::SubAgentThreadStartedInput; use crate::facts::ThreadInitializationMode; use crate::facts::ThreadStartTimingFact; -use crate::facts::ThreadStartType; use crate::facts::TrackEventsContext; use crate::facts::TurnResolvedConfigFact; use crate::facts::TurnStatus; @@ -1332,7 +1331,6 @@ fn thread_initialized_event_serializes_expected_shape() { subagent_source: None, parent_thread_id: None, thread_start_timing: ThreadStartTimingEventParams { - thread_start_type: Some(ThreadStartType::New), thread_start_duration_ms: Some(321), thread_start_prepare_duration_ms: Some(111), thread_start_spawn_duration_ms: Some(123), @@ -1369,7 +1367,6 @@ fn thread_initialized_event_serializes_expected_shape() { "initialization_mode": "new", "subagent_source": null, "parent_thread_id": null, - "thread_start_type": "new", "thread_start_duration_ms": 321, "thread_start_prepare_duration_ms": 111, "thread_start_spawn_duration_ms": 123, @@ -1688,7 +1685,6 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize payload[0]["event_params"]["runtime"]["runtime_arch"], "x86_64" ); - assert_eq!(payload[0]["event_params"]["thread_start_type"], json!(null)); assert_eq!( payload[0]["event_params"]["thread_start_duration_ms"], json!(null) @@ -1757,7 +1753,6 @@ async fn thread_start_timing_fact_enriches_thread_initialized_event() { AnalyticsFact::Custom(CustomAnalyticsFact::ThreadStartTiming( ThreadStartTimingFact { thread_id: "thread-1".to_string(), - thread_start_type: ThreadStartType::Cleared, duration_ms: 222, prepare_duration_ms: 12, spawn_duration_ms: 123, @@ -1782,72 +1777,6 @@ async fn thread_start_timing_fact_enriches_thread_initialized_event() { let payload = serde_json::to_value(&events).expect("serialize events"); assert_eq!(payload[0]["event_type"], json!("codex_thread_initialized")); - assert_eq!( - payload[0]["event_params"]["thread_start_type"], - json!("cleared") - ); - assert_eq!( - payload[0]["event_params"]["thread_start_duration_ms"], - json!(222) - ); - assert_eq!( - payload[0]["event_params"]["thread_start_prepare_duration_ms"], - json!(12) - ); - assert_eq!( - payload[0]["event_params"]["thread_start_spawn_duration_ms"], - json!(123) - ); - assert_eq!( - payload[0]["event_params"]["thread_start_finalize_duration_ms"], - json!(87) - ); -} - -#[tokio::test] -async fn thread_start_timing_fact_enriches_resumed_thread_initialized_event() { - let mut reducer = AnalyticsReducer::default(); - let mut events = Vec::new(); - - ingest_initialize(&mut reducer, &mut events).await; - reducer - .ingest( - AnalyticsFact::Custom(CustomAnalyticsFact::ThreadStartTiming( - ThreadStartTimingFact { - thread_id: "thread-1".to_string(), - thread_start_type: ThreadStartType::Resumed, - duration_ms: 222, - prepare_duration_ms: 12, - spawn_duration_ms: 123, - finalize_duration_ms: 87, - }, - )), - &mut events, - ) - .await; - reducer - .ingest( - AnalyticsFact::ClientResponse { - connection_id: 7, - request_id: RequestId::Integer(2), - response: Box::new(sample_thread_resume_response( - "thread-1", /*ephemeral*/ true, "gpt-5", - )), - }, - &mut events, - ) - .await; - - let payload = serde_json::to_value(&events).expect("serialize events"); - assert_eq!(payload[0]["event_type"], json!("codex_thread_initialized")); - assert_eq!( - payload[0]["event_params"]["initialization_mode"], - json!("resumed") - ); - assert_eq!( - payload[0]["event_params"]["thread_start_type"], - json!("resumed") - ); assert_eq!( payload[0]["event_params"]["thread_start_duration_ms"], json!(222) diff --git a/codex-rs/analytics/src/events.rs b/codex-rs/analytics/src/events.rs index b79e53b6d1..870de097c6 100644 --- a/codex-rs/analytics/src/events.rs +++ b/codex-rs/analytics/src/events.rs @@ -14,7 +14,6 @@ use crate::facts::InvocationType; use crate::facts::PluginState; use crate::facts::SubAgentThreadStartedInput; use crate::facts::ThreadInitializationMode; -use crate::facts::ThreadStartType; use crate::facts::TrackEventsContext; use crate::facts::TurnStatus; use crate::facts::TurnSteerRejectionReason; @@ -162,7 +161,6 @@ pub(crate) struct CodexAppServerStartedEventRequest { #[derive(Default, Serialize)] pub(crate) struct ThreadStartTimingEventParams { - pub(crate) thread_start_type: Option, pub(crate) thread_start_duration_ms: Option, pub(crate) thread_start_prepare_duration_ms: Option, pub(crate) thread_start_spawn_duration_ms: Option, diff --git a/codex-rs/analytics/src/facts.rs b/codex-rs/analytics/src/facts.rs index eaf256ac8e..e22e6d241e 100644 --- a/codex-rs/analytics/src/facts.rs +++ b/codex-rs/analytics/src/facts.rs @@ -92,15 +92,6 @@ pub enum ThreadInitializationMode { Resumed, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum ThreadStartType { - New, - Cleared, - Forked, - Resumed, -} - #[derive(Clone)] pub struct TurnTokenUsageFact { pub turn_id: String, @@ -292,7 +283,6 @@ pub(crate) struct AppServerStartedInput { pub struct ThreadStartTimingFact { pub thread_id: String, - pub thread_start_type: ThreadStartType, pub duration_ms: u64, pub prepare_duration_ms: u64, pub spawn_duration_ms: u64, diff --git a/codex-rs/analytics/src/lib.rs b/codex-rs/analytics/src/lib.rs index 547303598b..d4bbcabc11 100644 --- a/codex-rs/analytics/src/lib.rs +++ b/codex-rs/analytics/src/lib.rs @@ -39,7 +39,6 @@ pub use facts::SkillInvocation; pub use facts::SubAgentThreadStartedInput; pub use facts::ThreadInitializationMode; pub use facts::ThreadStartTimingFact; -pub use facts::ThreadStartType; pub use facts::TrackEventsContext; pub use facts::TurnResolvedConfigFact; pub use facts::TurnStatus; diff --git a/codex-rs/analytics/src/reducer.rs b/codex-rs/analytics/src/reducer.rs index beb768fe6b..1476dc5f3e 100644 --- a/codex-rs/analytics/src/reducer.rs +++ b/codex-rs/analytics/src/reducer.rs @@ -76,7 +76,6 @@ use crate::facts::SkillInvokedInput; use crate::facts::SubAgentThreadStartedInput; use crate::facts::ThreadInitializationMode; use crate::facts::ThreadStartTimingFact; -use crate::facts::ThreadStartType; use crate::facts::TurnResolvedConfigFact; use crate::facts::TurnStatus; use crate::facts::TurnSteerRejectionReason; @@ -158,7 +157,6 @@ struct ThreadAnalyticsState { #[derive(Clone, Copy)] struct ThreadStartTimingState { - thread_start_type: ThreadStartType, duration_ms: u64, prepare_duration_ms: u64, spawn_duration_ms: u64, @@ -580,7 +578,6 @@ impl AnalyticsReducer { fn ingest_thread_start_timing(&mut self, input: ThreadStartTimingFact) { let thread_start_timing = ThreadStartTimingState { - thread_start_type: input.thread_start_type, duration_ms: input.duration_ms, prepare_duration_ms: input.prepare_duration_ms, spawn_duration_ms: input.spawn_duration_ms, @@ -1297,7 +1294,9 @@ impl AnalyticsReducer { let thread_state = self.threads.entry(thread_id.clone()).or_default(); thread_state.connection_id = Some(connection_id); thread_state.metadata = Some(thread_metadata.clone()); - let thread_start_timing = thread_state.thread_start_timing; + let thread_start_timing = matches!(initialization_mode, ThreadInitializationMode::New) + .then_some(thread_state.thread_start_timing) + .flatten(); out.push(TrackEventRequest::ThreadInitialized( ThreadInitializedEvent { event_type: "codex_thread_initialized", @@ -1313,7 +1312,6 @@ impl AnalyticsReducer { parent_thread_id: thread_metadata.parent_thread_id, thread_start_timing: match thread_start_timing { Some(timing) => ThreadStartTimingEventParams { - thread_start_type: Some(timing.thread_start_type), thread_start_duration_ms: Some(timing.duration_ms), thread_start_prepare_duration_ms: Some(timing.prepare_duration_ms), thread_start_spawn_duration_ms: Some(timing.spawn_duration_ms), diff --git a/codex-rs/app-server/tests/suite/v2/analytics.rs b/codex-rs/app-server/tests/suite/v2/analytics.rs index c18f57ea2c..06adf862b9 100644 --- a/codex-rs/app-server/tests/suite/v2/analytics.rs +++ b/codex-rs/app-server/tests/suite/v2/analytics.rs @@ -142,7 +142,6 @@ pub(crate) fn assert_basic_thread_initialized_event( expected_model: &str, initialization_mode: &str, expected_thread_source: &str, - expected_thread_start_type: &str, ) { assert_eq!(event["event_params"]["thread_id"], thread_id); assert_eq!( @@ -175,9 +174,5 @@ pub(crate) fn assert_basic_thread_initialized_event( event["event_params"]["initialization_mode"], initialization_mode ); - assert_eq!( - event["event_params"]["thread_start_type"], - expected_thread_start_type - ); assert!(event["event_params"]["created_at"].as_u64().is_some()); } diff --git a/codex-rs/app-server/tests/suite/v2/thread_fork.rs b/codex-rs/app-server/tests/suite/v2/thread_fork.rs index 2260488b65..098b26a76e 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_fork.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_fork.rs @@ -406,22 +406,7 @@ async fn thread_fork_tracks_thread_initialized_analytics() -> Result<()> { let event = wait_for_analytics_event(&server, ANALYTICS_TEST_TIMEOUT, "codex_thread_initialized") .await?; - assert_basic_thread_initialized_event( - &event, - &thread.id, - "mock-model", - "forked", - "user", - "forked", - ); - let thread_start_timings = [ - "thread_start_duration_ms", - "thread_start_prepare_duration_ms", - "thread_start_spawn_duration_ms", - "thread_start_finalize_duration_ms", - ] - .map(|field| event["event_params"][field].as_u64().is_some()); - assert_eq!(thread_start_timings, [true, true, true, true]); + assert_basic_thread_initialized_event(&event, &thread.id, "mock-model", "forked", "user"); Ok(()) } diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index d1bfd49bbd..07706d36e2 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -422,23 +422,8 @@ async fn thread_resume_tracks_thread_initialized_analytics() -> Result<()> { let event = wait_for_analytics_event(&server, ANALYTICS_TEST_TIMEOUT, "codex_thread_initialized") .await?; - assert_basic_thread_initialized_event( - &event, - &thread.id, - "gpt-5.3-codex", - "resumed", - "user", - "resumed", - ); + assert_basic_thread_initialized_event(&event, &thread.id, "gpt-5.3-codex", "resumed", "user"); assert_eq!(event["event_params"]["thread_source"], "user"); - let thread_start_timings = [ - "thread_start_duration_ms", - "thread_start_prepare_duration_ms", - "thread_start_spawn_duration_ms", - "thread_start_finalize_duration_ms", - ] - .map(|field| event["event_params"][field].as_u64().is_some()); - assert_eq!(thread_start_timings, [true, true, true, true]); Ok(()) } diff --git a/codex-rs/app-server/tests/suite/v2/thread_start.rs b/codex-rs/app-server/tests/suite/v2/thread_start.rs index 46c4c4ce87..91ebb001d6 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_start.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_start.rs @@ -18,7 +18,6 @@ use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::ThreadSource; use codex_app_server_protocol::ThreadStartParams; use codex_app_server_protocol::ThreadStartResponse; -use codex_app_server_protocol::ThreadStartSource; use codex_app_server_protocol::ThreadStartedNotification; use codex_app_server_protocol::ThreadStatus; use codex_app_server_protocol::ThreadStatusChangedNotification; @@ -440,7 +439,7 @@ 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", "new"); + assert_basic_thread_initialized_event(event, &thread.id, "mock-model", "new", "user"); let thread_start_timings = [ "thread_start_duration_ms", "thread_start_prepare_duration_ms", @@ -452,44 +451,6 @@ async fn thread_start_tracks_thread_initialized_analytics() -> Result<()> { Ok(()) } -#[tokio::test] -async fn thread_start_clear_tracks_thread_initialized_analytics() -> Result<()> { - let server = create_mock_responses_server_repeating_assistant("Done").await; - - let codex_home = TempDir::new()?; - create_config_toml_with_chatgpt_base_url(codex_home.path(), &server.uri(), &server.uri())?; - mount_analytics_capture(&server, codex_home.path()).await?; - - let mut mcp = McpProcess::new_without_managed_config(codex_home.path()).await?; - timeout(ANALYTICS_TEST_TIMEOUT, mcp.initialize()).await??; - - let req_id = mcp - .send_thread_start_request(ThreadStartParams { - session_start_source: Some(ThreadStartSource::Clear), - thread_source: Some(ThreadSource::User), - ..Default::default() - }) - .await?; - let resp: JSONRPCResponse = timeout( - ANALYTICS_TEST_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(req_id)), - ) - .await??; - let ThreadStartResponse { thread, .. } = to_response::(resp)?; - - 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", - "new", - "user", - "cleared", - ); - Ok(()) -} - #[tokio::test] async fn thread_start_respects_project_config_from_cwd() -> Result<()> { let server = create_mock_responses_server_repeating_assistant("Done").await; diff --git a/codex-rs/core/src/thread_manager.rs b/codex-rs/core/src/thread_manager.rs index 5d5fee36f0..2d79bb5c63 100644 --- a/codex-rs/core/src/thread_manager.rs +++ b/codex-rs/core/src/thread_manager.rs @@ -17,7 +17,6 @@ use crate::tasks::InterruptedTurnHistoryMarker; use crate::tasks::interrupted_turn_history_marker; use crate::thread_start_timing::ThreadStartTiming; use codex_analytics::AnalyticsEventsClient; -use codex_analytics::ThreadStartType; use codex_app_server_protocol::ThreadHistoryBuilder; use codex_app_server_protocol::TurnStatus; use codex_core_plugins::PluginsManager; @@ -1190,12 +1189,7 @@ impl ThreadManagerState { user_shell_override: Option, ) -> CodexResult { let is_resumed_thread = matches!(&initial_history, InitialHistory::Resumed(_)); - let mut thread_start_timing = ThreadStartTiming::start(match &initial_history { - InitialHistory::New => ThreadStartType::New, - InitialHistory::Cleared => ThreadStartType::Cleared, - InitialHistory::Forked(_) => ThreadStartType::Forked, - InitialHistory::Resumed(_) => ThreadStartType::Resumed, - }); + let mut thread_start_timing = ThreadStartTiming::start(); if let InitialHistory::Resumed(resumed) = &initial_history { let mut threads = self.threads.write().await; if let Some(thread) = threads.get(&resumed.conversation_id).cloned() { diff --git a/codex-rs/core/src/thread_start_timing.rs b/codex-rs/core/src/thread_start_timing.rs index 6697b46ba2..0cf5457d5a 100644 --- a/codex-rs/core/src/thread_start_timing.rs +++ b/codex-rs/core/src/thread_start_timing.rs @@ -2,11 +2,9 @@ use std::time::Duration; use std::time::Instant; use codex_analytics::ThreadStartTimingFact; -use codex_analytics::ThreadStartType; #[derive(Debug)] pub(crate) struct ThreadStartTiming { - thread_start_type: ThreadStartType, phase_started_at: Instant, prepare_duration: Option, spawn_duration: Option, @@ -14,9 +12,8 @@ pub(crate) struct ThreadStartTiming { } impl ThreadStartTiming { - pub(crate) fn start(thread_start_type: ThreadStartType) -> Self { + pub(crate) fn start() -> Self { Self { - thread_start_type, phase_started_at: Instant::now(), prepare_duration: None, spawn_duration: None, @@ -42,7 +39,6 @@ impl ThreadStartTiming { let finalize_duration = self.finalize_duration.unwrap_or_default(); ThreadStartTimingFact { thread_id, - thread_start_type: self.thread_start_type, duration_ms: duration_ms(prepare_duration + spawn_duration + finalize_duration), prepare_duration_ms: duration_ms(prepare_duration), spawn_duration_ms: duration_ms(spawn_duration),