mirror of
https://github.com/openai/codex.git
synced 2026-10-28 16:06:15 +00:00
Include terminal errors in turn completion events (#32280)
## What changed - Add an optional `error` payload to `TurnCompleteEvent` and omit it when a turn completes without an error. - Preserve the full terminal `ErrorEvent` through the turn lifecycle so the completion event includes the same error details emitted separately by `EventMsg::Error`. ## Testing - Extend the stream-error integration test to verify that `TurnCompleteEvent` contains the emitted error and that the next turn can still proceed. GitOrigin-RevId: 8d32942a44132763076130102d8b89ab356d59d0
This commit is contained in:
@@ -161,6 +161,7 @@ async fn mark_thread_completed(thread: &CodexThread) {
|
||||
turn_id: turn.sub_id.clone(),
|
||||
started_at: None,
|
||||
last_agent_message: Some("done".to_string()),
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
|
||||
@@ -351,6 +351,7 @@ async fn on_event_updates_status_from_task_complete() {
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at: None,
|
||||
last_agent_message: Some("done".to_string()),
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -2029,6 +2030,7 @@ async fn multi_agent_v2_completion_ignores_dead_direct_parent() {
|
||||
turn_id: tester_turn.sub_id.clone(),
|
||||
started_at: None,
|
||||
last_agent_message: Some("done".to_string()),
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -2117,6 +2119,7 @@ async fn multi_agent_v2_completion_queues_message_for_direct_parent() {
|
||||
turn_id: tester_turn.sub_id.clone(),
|
||||
started_at: None,
|
||||
last_agent_message: Some("done".to_string()),
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
|
||||
@@ -1179,6 +1179,7 @@ mod tests {
|
||||
turn_id: turn_id.to_string(),
|
||||
started_at: None,
|
||||
last_agent_message: last_agent_message.map(str::to_string),
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms,
|
||||
|
||||
@@ -2234,6 +2234,7 @@ async fn guardian_reused_trunk_ignores_stale_prior_turn_completion() -> anyhow::
|
||||
"{\"risk_level\":\"high\",\"user_authorization\":\"low\",\"outcome\":\"deny\",\"rationale\":\"stale guardian rationale\"}"
|
||||
.to_string(),
|
||||
),
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: Some(1),
|
||||
|
||||
@@ -1776,7 +1776,7 @@ impl Session {
|
||||
.terminal_error
|
||||
.lock()
|
||||
.await
|
||||
.replace(error.message.clone());
|
||||
.replace(error.clone());
|
||||
}
|
||||
self.services
|
||||
.rollout_thread_trace
|
||||
@@ -1834,7 +1834,7 @@ impl Session {
|
||||
|
||||
let status = match turn_context.terminal_error.lock().await.take() {
|
||||
Some(error) => {
|
||||
let status = AgentStatus::Errored(error);
|
||||
let status = AgentStatus::Errored(error.message);
|
||||
self.agent_status.send_replace(status.clone());
|
||||
status
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ fn completed_user_turn_rollout(
|
||||
codex_protocol::protocol::TurnCompleteEvent {
|
||||
turn_id,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
started_at: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
@@ -266,6 +267,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
|
||||
codex_protocol::protocol::TurnCompleteEvent {
|
||||
turn_id,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
started_at: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
@@ -343,6 +345,7 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_com
|
||||
turn_id: first_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -378,6 +381,7 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_com
|
||||
turn_id: rolled_back_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -458,6 +462,7 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_inc
|
||||
turn_id: first_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -556,6 +561,7 @@ async fn reconstruct_history_rollback_skips_non_user_turns_for_history_and_metad
|
||||
turn_id: first_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -587,6 +593,7 @@ async fn reconstruct_history_rollback_skips_non_user_turns_for_history_and_metad
|
||||
turn_id: second_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -607,6 +614,7 @@ async fn reconstruct_history_rollback_skips_non_user_turns_for_history_and_metad
|
||||
turn_id: standalone_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -685,6 +693,7 @@ async fn reconstruct_history_rollback_counts_inter_agent_assistant_turns() {
|
||||
turn_id: first_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -707,6 +716,7 @@ async fn reconstruct_history_rollback_counts_inter_agent_assistant_turns() {
|
||||
turn_id: assistant_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -780,6 +790,7 @@ async fn reconstruct_history_rollback_clears_history_and_metadata_when_exceeding
|
||||
turn_id: only_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -834,6 +845,7 @@ async fn record_initial_history_resumed_rollback_skips_only_user_turns() {
|
||||
turn_id: user_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -854,6 +866,7 @@ async fn record_initial_history_resumed_rollback_skips_only_user_turns() {
|
||||
turn_id: standalone_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -912,6 +925,7 @@ async fn record_initial_history_resumed_rollback_drops_incomplete_user_turn_comp
|
||||
turn_id: previous_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -1242,6 +1256,7 @@ async fn reconstruct_history_legacy_compaction_without_replacement_history_clear
|
||||
turn_id: current_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -1323,6 +1338,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
turn_id: previous_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -1436,6 +1452,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
|
||||
turn_id: previous_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -1560,6 +1577,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
|
||||
turn_id: previous_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -1599,6 +1617,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
|
||||
turn_id: current_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -1690,6 +1709,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
|
||||
turn_id: previous_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -1860,6 +1880,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
|
||||
turn_id: previous_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
|
||||
@@ -3136,6 +3136,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
|
||||
codex_protocol::protocol::TurnCompleteEvent {
|
||||
turn_id,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
started_at: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
@@ -3338,6 +3339,7 @@ async fn thread_rollback_recomputes_previous_turn_settings_and_reference_context
|
||||
turn_id: first_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -3368,6 +3370,7 @@ async fn thread_rollback_recomputes_previous_turn_settings_and_reference_context
|
||||
turn_id: rolled_back_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -3458,6 +3461,7 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio
|
||||
turn_id: first_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -3483,6 +3487,7 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio
|
||||
turn_id: compact_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -3516,6 +3521,7 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio
|
||||
turn_id: rolled_back_turn_id,
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -3590,6 +3596,7 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() {
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -3618,6 +3625,7 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() {
|
||||
turn_id: "turn-2".to_string(),
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -3646,6 +3654,7 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() {
|
||||
turn_id: "turn-3".to_string(),
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -9762,6 +9771,7 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input()
|
||||
EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
turn_id,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
time_to_first_token_ms: None,
|
||||
..
|
||||
}) if turn_id == tc.sub_id
|
||||
|
||||
@@ -9,6 +9,7 @@ use codex_protocol::SessionId;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::protocol::ErrorEvent;
|
||||
use codex_protocol::protocol::MultiAgentVersion;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
@@ -140,7 +141,7 @@ pub struct TurnContext {
|
||||
pub(crate) extension_data: Arc<codex_extension_api::ExtensionData>,
|
||||
pub(crate) turn_skills: TurnSkillsContext,
|
||||
pub(crate) turn_timing_state: Arc<TurnTimingState>,
|
||||
pub(crate) terminal_error: Arc<Mutex<Option<String>>>,
|
||||
pub(crate) terminal_error: Arc<Mutex<Option<ErrorEvent>>>,
|
||||
pub(crate) server_model_warning_emitted: AtomicBool,
|
||||
pub(crate) model_verification_emitted: AtomicBool,
|
||||
}
|
||||
|
||||
@@ -766,11 +766,13 @@ impl Session {
|
||||
.turn_timing_state
|
||||
.time_to_first_token_ms()
|
||||
.await;
|
||||
let error = turn_context.terminal_error.lock().await.clone();
|
||||
self.emit_turn_stop_lifecycle(turn_context.extension_data.as_ref())
|
||||
.await;
|
||||
EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
last_agent_message,
|
||||
error,
|
||||
started_at,
|
||||
completed_at,
|
||||
duration_ms,
|
||||
|
||||
@@ -84,6 +84,7 @@ fn turn_completed(turn_id: &str) -> RolloutItem {
|
||||
turn_id: turn_id.to_string(),
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
|
||||
@@ -1560,6 +1560,7 @@ async fn multi_agent_v2_list_agents_returns_completed_status_without_encrypted_s
|
||||
turn_id: child_turn.sub_id.clone(),
|
||||
started_at: None,
|
||||
last_agent_message: Some("done".to_string()),
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -2010,6 +2011,7 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn()
|
||||
turn_id: first_turn.sub_id.clone(),
|
||||
started_at: None,
|
||||
last_agent_message: Some("first done".to_string()),
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
@@ -2052,6 +2054,7 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn()
|
||||
turn_id: second_turn.sub_id.clone(),
|
||||
started_at: None,
|
||||
last_agent_message: Some("second done".to_string()),
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
|
||||
Reference in New Issue
Block a user