From 85a28f9b27fccc5bc42449a3efd8e8003cadba3a Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Thu, 20 Aug 2026 11:57:45 -0700 Subject: [PATCH] Said the outcome is unknown, not that the call was aborted. The output synthesized for a tool call with no result read "aborted", which tells the model the command did not take effect. Nothing here knows that. The result is missing because something stopped between dispatch and the write, and after a hard kill the command may well have run, so the model should check the state before repeating it. This does not touch the interrupt path, which writes its own richer output with the wall time, so the placeholder is only reached when nothing was recorded at all. --- .../core/src/context_manager/history_tests.rs | 33 ++++++++++++++----- codex-rs/core/src/context_manager/mod.rs | 2 +- .../core/src/context_manager/normalize.rs | 21 ++++++++++-- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/codex-rs/core/src/context_manager/history_tests.rs b/codex-rs/core/src/context_manager/history_tests.rs index fd9a4720bfa4..de8f69408911 100644 --- a/codex-rs/core/src/context_manager/history_tests.rs +++ b/codex-rs/core/src/context_manager/history_tests.rs @@ -1577,7 +1577,9 @@ fn normalize_adds_missing_output_for_function_call() { ResponseItem::FunctionCallOutput { id: None, call_id: "call-x".to_string(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ] @@ -1616,7 +1618,9 @@ fn normalize_adds_missing_output_for_custom_tool_call() { id: None, call_id: "tool-x".to_string(), name: None, - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ] @@ -1662,7 +1666,9 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id() { ResponseItem::FunctionCallOutput { id: None, call_id: "shell-1".to_string(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ] @@ -1767,7 +1773,9 @@ fn normalize_mixed_inserts_and_removals() { ResponseItem::FunctionCallOutput { id: None, call_id: "c1".to_string(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ResponseItem::CustomToolCall { @@ -1783,7 +1791,9 @@ fn normalize_mixed_inserts_and_removals() { id: None, call_id: "t1".to_string(), name: None, - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ResponseItem::LocalShellCall { @@ -1802,7 +1812,9 @@ fn normalize_mixed_inserts_and_removals() { ResponseItem::FunctionCallOutput { id: None, call_id: "s1".to_string(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ] @@ -1837,7 +1849,9 @@ fn normalize_adds_missing_output_for_function_call_inserts_output() { ResponseItem::FunctionCallOutput { id: None, call_id: "call-x".to_string(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ] @@ -1954,7 +1968,10 @@ fn normalize_fills_missing_output_for_custom_tool_call() { ); }; assert_eq!(call_id, "tool-x"); - assert_eq!(output.body.to_text().as_deref(), Some("aborted")); + assert_eq!( + output.body.to_text().as_deref(), + Some(crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER) + ); } #[test] diff --git a/codex-rs/core/src/context_manager/mod.rs b/codex-rs/core/src/context_manager/mod.rs index f2bdd89c4167..f2973442765b 100644 --- a/codex-rs/core/src/context_manager/mod.rs +++ b/codex-rs/core/src/context_manager/mod.rs @@ -1,5 +1,5 @@ mod history; -mod normalize; +pub(crate) mod normalize; pub(crate) mod updates; pub(crate) use history::ContextManager; diff --git a/codex-rs/core/src/context_manager/normalize.rs b/codex-rs/core/src/context_manager/normalize.rs index 0213f7d022c0..c7e08951dd88 100644 --- a/codex-rs/core/src/context_manager/normalize.rs +++ b/codex-rs/core/src/context_manager/normalize.rs @@ -18,6 +18,15 @@ const AUDIO_CONTENT_OMITTED_PLACEHOLDER: &str = // Changing this value would change model-visible IDs and invalidate prompt caches. const SYNTHETIC_OUTPUT_ID_NAMESPACE: Uuid = Uuid::from_u128(0x90d38d3e_6a5b_4d52_bfe2_2f1e634bfac4); +/// Stands in for a tool call that never reported a result, so the payload stays well formed. +/// +/// It says unknown rather than failed on purpose. The call is missing its result because +/// something stopped between dispatch and the write, and neither an interrupt nor a crash can +/// promise the command did not run first, so the model has to check before repeating it. +pub(crate) const MISSING_OUTPUT_PLACEHOLDER: &str = "The outcome of this call is unknown: it started and no \ + result was recorded. It may have taken effect. Check the current state before running it \ + again."; + /// Fill in the output of any tool call that never reported one. /// /// A gap here is expected, not a defect: an interrupt or a crash between dispatching a tool and @@ -62,7 +71,9 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec) ResponseItemEnvelope::new(ResponseItem::FunctionCallOutput { id: synthetic_output_id("fco", id.as_deref()), call_id: call_id.clone(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + MISSING_OUTPUT_PLACEHOLDER.to_string(), + ), internal_chat_message_metadata_passthrough: None, }), )); @@ -95,7 +106,9 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec) id: synthetic_output_id("ctco", id.as_deref()), call_id: call_id.clone(), name: None, - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + MISSING_OUTPUT_PLACEHOLDER.to_string(), + ), internal_chat_message_metadata_passthrough: None, }), )); @@ -112,7 +125,9 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec) ResponseItemEnvelope::new(ResponseItem::FunctionCallOutput { id: synthetic_output_id("fco", id.as_deref()), call_id: call_id.clone(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + MISSING_OUTPUT_PLACEHOLDER.to_string(), + ), internal_chat_message_metadata_passthrough: None, }), ));