From 3c682e782530e961b53a01db6b8d3105a68cb25d Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 16 Jun 2026 06:35:10 +0000 Subject: [PATCH 1/6] Preserve code mode output budgets in history --- codex-rs/core/src/context_manager/history.rs | 32 ++++++++- .../core/src/context_manager/history_tests.rs | 66 +++++++++++++++++++ 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/context_manager/history.rs b/codex-rs/core/src/context_manager/history.rs index 47f37a32f9c7..3f467551cd10 100644 --- a/codex-rs/core/src/context_manager/history.rs +++ b/codex-rs/core/src/context_manager/history.rs @@ -3,6 +3,8 @@ use crate::event_mapping::has_non_contextual_dev_message_content; use crate::event_mapping::is_contextual_dev_message_content; use crate::event_mapping::is_contextual_user_message_content; use crate::session::turn_context::TurnContext; +use crate::tools::code_mode::PUBLIC_TOOL_NAME; +use crate::tools::code_mode::WAIT_TOOL_NAME; use base64::Engine; use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; use codex_protocol::models::BaseInstructions; @@ -337,6 +339,7 @@ impl ContextManager { fn process_item(&self, item: &ResponseItem, policy: TruncationPolicy) -> ResponseItem { let policy_with_serialization_budget = policy * 1.2; + let output_is_pre_truncated = |call_id| self.output_is_pre_truncated(call_id); match item { ResponseItem::FunctionCallOutput { call_id, @@ -344,7 +347,11 @@ impl ContextManager { metadata, } => ResponseItem::FunctionCallOutput { call_id: call_id.clone(), - output: truncate_function_output_payload(output, policy_with_serialization_budget), + output: if output_is_pre_truncated(call_id) { + output.clone() + } else { + truncate_function_output_payload(output, policy_with_serialization_budget) + }, metadata: metadata.clone(), }, ResponseItem::CustomToolCallOutput { @@ -355,7 +362,11 @@ impl ContextManager { } => ResponseItem::CustomToolCallOutput { call_id: call_id.clone(), name: name.clone(), - output: truncate_function_output_payload(output, policy_with_serialization_budget), + output: if output_is_pre_truncated(call_id) { + output.clone() + } else { + truncate_function_output_payload(output, policy_with_serialization_budget) + }, metadata: metadata.clone(), }, ResponseItem::Message { .. } @@ -375,6 +386,23 @@ impl ContextManager { } } + fn output_is_pre_truncated(&self, call_id: &str) -> bool { + self.items.iter().rev().find_map(|item| match item { + ResponseItem::CustomToolCall { + call_id: candidate, + name, + .. + } if candidate == call_id => Some(name == PUBLIC_TOOL_NAME), + ResponseItem::FunctionCall { + call_id: candidate, + name, + namespace, + .. + } if candidate == call_id => Some(namespace.is_none() && name == WAIT_TOOL_NAME), + _ => None, + }) == Some(true) + } + /// Walk backward from a rollback cut and trim contiguous pre-turn context-update items. /// /// Returns the adjusted cut index after removing contextual developer/user items immediately diff --git a/codex-rs/core/src/context_manager/history_tests.rs b/codex-rs/core/src/context_manager/history_tests.rs index a98061b5b097..2b807beae267 100644 --- a/codex-rs/core/src/context_manager/history_tests.rs +++ b/codex-rs/core/src/context_manager/history_tests.rs @@ -1106,6 +1106,72 @@ fn record_items_truncates_custom_tool_call_output_content() { } } +#[test] +fn record_items_preserves_pre_truncated_exec_output() { + let mut history = ContextManager::new(); + let output = FunctionCallOutputPayload::from_text("exec output".repeat(100)); + let items = [ + ResponseItem::CustomToolCall { + id: None, + status: None, + call_id: "exec-call".to_string(), + name: crate::tools::code_mode::PUBLIC_TOOL_NAME.to_string(), + input: "text('hello')".to_string(), + metadata: None, + }, + ResponseItem::CustomToolCallOutput { + call_id: "exec-call".to_string(), + name: None, + output: output.clone(), + metadata: None, + }, + ]; + + history.record_items(items.iter(), TruncationPolicy::Tokens(1)); + + assert_eq!( + history.items[1], + ResponseItem::CustomToolCallOutput { + call_id: "exec-call".to_string(), + name: None, + output, + metadata: None, + } + ); +} + +#[test] +fn record_items_preserves_pre_truncated_wait_output() { + let mut history = ContextManager::new(); + let output = FunctionCallOutputPayload::from_text("wait output".repeat(100)); + let items = [ + ResponseItem::FunctionCall { + id: None, + name: crate::tools::code_mode::WAIT_TOOL_NAME.to_string(), + namespace: None, + arguments: r#"{"cell_id":"1"}"#.to_string(), + call_id: "wait-call".to_string(), + metadata: None, + }, + ResponseItem::FunctionCallOutput { + call_id: "wait-call".to_string(), + output: output.clone(), + metadata: None, + }, + ]; + + history.record_items(items.iter(), TruncationPolicy::Tokens(1)); + + assert_eq!( + history.items[1], + ResponseItem::FunctionCallOutput { + call_id: "wait-call".to_string(), + output, + metadata: None, + } + ); +} + #[test] fn record_items_respects_custom_token_limit() { let mut history = ContextManager::new(); From a1a28e7d585d10d225aac8b068363e29a943fa4d Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 16 Jun 2026 19:52:23 +0000 Subject: [PATCH 2/6] Test code mode variable truncation --- codex-rs/core/src/context_manager/history.rs | 32 +----- .../core/src/context_manager/history_tests.rs | 66 ------------ codex-rs/core/tests/suite/code_mode.rs | 100 +++++++++--------- 3 files changed, 52 insertions(+), 146 deletions(-) diff --git a/codex-rs/core/src/context_manager/history.rs b/codex-rs/core/src/context_manager/history.rs index 3f467551cd10..47f37a32f9c7 100644 --- a/codex-rs/core/src/context_manager/history.rs +++ b/codex-rs/core/src/context_manager/history.rs @@ -3,8 +3,6 @@ use crate::event_mapping::has_non_contextual_dev_message_content; use crate::event_mapping::is_contextual_dev_message_content; use crate::event_mapping::is_contextual_user_message_content; use crate::session::turn_context::TurnContext; -use crate::tools::code_mode::PUBLIC_TOOL_NAME; -use crate::tools::code_mode::WAIT_TOOL_NAME; use base64::Engine; use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; use codex_protocol::models::BaseInstructions; @@ -339,7 +337,6 @@ impl ContextManager { fn process_item(&self, item: &ResponseItem, policy: TruncationPolicy) -> ResponseItem { let policy_with_serialization_budget = policy * 1.2; - let output_is_pre_truncated = |call_id| self.output_is_pre_truncated(call_id); match item { ResponseItem::FunctionCallOutput { call_id, @@ -347,11 +344,7 @@ impl ContextManager { metadata, } => ResponseItem::FunctionCallOutput { call_id: call_id.clone(), - output: if output_is_pre_truncated(call_id) { - output.clone() - } else { - truncate_function_output_payload(output, policy_with_serialization_budget) - }, + output: truncate_function_output_payload(output, policy_with_serialization_budget), metadata: metadata.clone(), }, ResponseItem::CustomToolCallOutput { @@ -362,11 +355,7 @@ impl ContextManager { } => ResponseItem::CustomToolCallOutput { call_id: call_id.clone(), name: name.clone(), - output: if output_is_pre_truncated(call_id) { - output.clone() - } else { - truncate_function_output_payload(output, policy_with_serialization_budget) - }, + output: truncate_function_output_payload(output, policy_with_serialization_budget), metadata: metadata.clone(), }, ResponseItem::Message { .. } @@ -386,23 +375,6 @@ impl ContextManager { } } - fn output_is_pre_truncated(&self, call_id: &str) -> bool { - self.items.iter().rev().find_map(|item| match item { - ResponseItem::CustomToolCall { - call_id: candidate, - name, - .. - } if candidate == call_id => Some(name == PUBLIC_TOOL_NAME), - ResponseItem::FunctionCall { - call_id: candidate, - name, - namespace, - .. - } if candidate == call_id => Some(namespace.is_none() && name == WAIT_TOOL_NAME), - _ => None, - }) == Some(true) - } - /// Walk backward from a rollback cut and trim contiguous pre-turn context-update items. /// /// Returns the adjusted cut index after removing contextual developer/user items immediately diff --git a/codex-rs/core/src/context_manager/history_tests.rs b/codex-rs/core/src/context_manager/history_tests.rs index 2b807beae267..a98061b5b097 100644 --- a/codex-rs/core/src/context_manager/history_tests.rs +++ b/codex-rs/core/src/context_manager/history_tests.rs @@ -1106,72 +1106,6 @@ fn record_items_truncates_custom_tool_call_output_content() { } } -#[test] -fn record_items_preserves_pre_truncated_exec_output() { - let mut history = ContextManager::new(); - let output = FunctionCallOutputPayload::from_text("exec output".repeat(100)); - let items = [ - ResponseItem::CustomToolCall { - id: None, - status: None, - call_id: "exec-call".to_string(), - name: crate::tools::code_mode::PUBLIC_TOOL_NAME.to_string(), - input: "text('hello')".to_string(), - metadata: None, - }, - ResponseItem::CustomToolCallOutput { - call_id: "exec-call".to_string(), - name: None, - output: output.clone(), - metadata: None, - }, - ]; - - history.record_items(items.iter(), TruncationPolicy::Tokens(1)); - - assert_eq!( - history.items[1], - ResponseItem::CustomToolCallOutput { - call_id: "exec-call".to_string(), - name: None, - output, - metadata: None, - } - ); -} - -#[test] -fn record_items_preserves_pre_truncated_wait_output() { - let mut history = ContextManager::new(); - let output = FunctionCallOutputPayload::from_text("wait output".repeat(100)); - let items = [ - ResponseItem::FunctionCall { - id: None, - name: crate::tools::code_mode::WAIT_TOOL_NAME.to_string(), - namespace: None, - arguments: r#"{"cell_id":"1"}"#.to_string(), - call_id: "wait-call".to_string(), - metadata: None, - }, - ResponseItem::FunctionCallOutput { - call_id: "wait-call".to_string(), - output: output.clone(), - metadata: None, - }, - ]; - - history.record_items(items.iter(), TruncationPolicy::Tokens(1)); - - assert_eq!( - history.items[1], - ResponseItem::FunctionCallOutput { - call_id: "wait-call".to_string(), - output, - metadata: None, - } - ); -} - #[test] fn record_items_respects_custom_token_limit() { let mut history = ContextManager::new(); diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index 25fc3ccf2b3c..5d57640ddf5f 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -964,7 +964,7 @@ text(result.output); &custom_tool_output_items(&second_mock.single_request(), "call-1"), /*index*/ 1 ), - "Total output lines: 1\n\n0123456789…5 tokens truncated…0123456789" + "Warning: truncated output (original token count: 10)\nTotal output lines: 1\n\n0123456789…5 tokens truncated…0123456789" ); Ok(()) @@ -972,7 +972,7 @@ text(result.output); #[cfg_attr(windows, ignore = "no exec_command on Windows")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn code_mode_exec_explicit_max_above_default_preserves_output() -> Result<()> { +async fn code_mode_exec_explicit_max_above_default_preserves_variable() -> Result<()> { // TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode. skip_if_wine_exec!( Ok(()), @@ -981,7 +981,7 @@ async fn code_mode_exec_explicit_max_above_default_preserves_output() -> Result< skip_if_no_network!(Ok(())); let server = responses::start_mock_server().await; - let (_test, second_mock) = run_code_mode_turn( + let (_test, second_mock) = run_code_mode_turn_with_model_and_config( &server, "use exec_command from code mode", r#"// @exec: {"max_output_tokens": 20000} @@ -989,17 +989,18 @@ const result = await tools.exec_command({ cmd: "python3 -c \"import sys; sys.stdout.write('x' * 50000)\"", max_output_tokens: 20000 }); -text(result.output); +text(`Variable truncated: ${result.output.length === 50000 ? "False" : "True"}. Variable: ${result.output}`); "#, + "gpt-5.4", + |_| {}, ) .await?; - assert_eq!( - text_item( - &custom_tool_output_items(&second_mock.single_request(), "call-1"), - /*index*/ 1 - ), - "x".repeat(50_000) + let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); + let output = text_item(&items, /*index*/ 1); + assert_regex_match( + r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", + output, ); Ok(()) @@ -1007,7 +1008,7 @@ text(result.output); #[cfg_attr(windows, ignore = "no exec_command on Windows")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn code_mode_exec_explicit_max_above_default_truncates_larger_output() -> Result<()> { +async fn code_mode_exec_explicit_max_above_default_truncates_larger_variable() -> Result<()> { // TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode. skip_if_wine_exec!( Ok(()), @@ -1016,7 +1017,7 @@ async fn code_mode_exec_explicit_max_above_default_truncates_larger_output() -> skip_if_no_network!(Ok(())); let server = responses::start_mock_server().await; - let (_test, second_mock) = run_code_mode_turn( + let (_test, second_mock) = run_code_mode_turn_with_model_and_config( &server, "use exec_command from code mode", r#"// @exec: {"max_output_tokens": 25000} @@ -1024,21 +1025,19 @@ const result = await tools.exec_command({ cmd: "python3 -c \"import sys; sys.stdout.write('A' * 90000)\"", max_output_tokens: 20000 }); -text(result.output); +const variableTruncated = result.output.includes("…2500 tokens truncated…"); +text(`Variable truncated: ${variableTruncated ? "True" : "False"}. Variable: ${result.output}`); "#, + "gpt-5.4", + |_| {}, ) .await?; - assert_eq!( - text_item( - &custom_tool_output_items(&second_mock.single_request(), "call-1"), - /*index*/ 1 - ), - format!( - "Warning: truncated output (original token count: 22500)\nTotal output lines: 1\n\n{}…2500 tokens truncated…{}", - "A".repeat(40_000), - "A".repeat(40_000) - ) + let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); + let output = text_item(&items, /*index*/ 1); + assert_regex_match( + r"(?s)^Variable truncated: True\. Variable: .*…\d+ tokens truncated…A+$", + output, ); Ok(()) @@ -1046,7 +1045,7 @@ text(result.output); #[cfg_attr(windows, ignore = "no exec_command on Windows")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn code_mode_exec_explicit_max_above_truncation_policy_preserves_output() -> Result<()> { +async fn code_mode_exec_explicit_max_above_truncation_policy_preserves_variable() -> Result<()> { // TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode. skip_if_wine_exec!( Ok(()), @@ -1055,7 +1054,7 @@ async fn code_mode_exec_explicit_max_above_truncation_policy_preserves_output() skip_if_no_network!(Ok(())); let server = responses::start_mock_server().await; - let (_test, second_mock) = run_code_mode_turn_with_config( + let (_test, second_mock) = run_code_mode_turn_with_model_and_config( &server, "use exec_command from code mode", r#"// @exec: {"max_output_tokens": 20000} @@ -1063,20 +1062,20 @@ const result = await tools.exec_command({ cmd: "python3 -c \"import sys; sys.stdout.write('x' * 50000)\"", max_output_tokens: 20000 }); -text(result.output); +text(`Variable truncated: ${result.output.length === 50000 ? "False" : "True"}. Variable: ${result.output}`); "#, + "gpt-5.4", |config| { config.tool_output_token_limit = Some(50); }, ) .await?; - assert_eq!( - text_item( - &custom_tool_output_items(&second_mock.single_request(), "call-1"), - /*index*/ 1 - ), - "x".repeat(50_000) + let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); + let output = text_item(&items, /*index*/ 1); + assert_regex_match( + r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", + output, ); Ok(()) @@ -1084,7 +1083,7 @@ text(result.output); #[cfg_attr(windows, ignore = "no exec_command on Windows")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn code_mode_exec_without_max_preserves_output_beyond_default() -> Result<()> { +async fn code_mode_exec_without_max_preserves_variable_beyond_default() -> Result<()> { // TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode. skip_if_wine_exec!( Ok(()), @@ -1093,24 +1092,25 @@ async fn code_mode_exec_without_max_preserves_output_beyond_default() -> Result< skip_if_no_network!(Ok(())); let server = responses::start_mock_server().await; - let (_test, second_mock) = run_code_mode_turn( + let (_test, second_mock) = run_code_mode_turn_with_model_and_config( &server, "use exec_command from code mode", r#"// @exec: {"max_output_tokens": 20000} const result = await tools.exec_command({ cmd: "python3 -c \"import sys; sys.stdout.write('x' * 50000)\"" }); -text(result.output); +text(`Variable truncated: ${result.output.length === 50000 ? "False" : "True"}. Variable: ${result.output}`); "#, + "gpt-5.4", + |_| {}, ) .await?; - assert_eq!( - text_item( - &custom_tool_output_items(&second_mock.single_request(), "call-1"), - /*index*/ 1 - ), - "x".repeat(50_000) + let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); + let output = text_item(&items, /*index*/ 1); + assert_regex_match( + r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", + output, ); Ok(()) @@ -1118,7 +1118,7 @@ text(result.output); #[cfg_attr(windows, ignore = "no exec_command on Windows")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn code_mode_exec_without_max_preserves_output_beyond_truncation_policy() -> Result<()> { +async fn code_mode_exec_without_max_preserves_variable_beyond_truncation_policy() -> Result<()> { // TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode. skip_if_wine_exec!( Ok(()), @@ -1127,27 +1127,27 @@ async fn code_mode_exec_without_max_preserves_output_beyond_truncation_policy() skip_if_no_network!(Ok(())); let server = responses::start_mock_server().await; - let (_test, second_mock) = run_code_mode_turn_with_config( + let (_test, second_mock) = run_code_mode_turn_with_model_and_config( &server, "use exec_command from code mode", r#"// @exec: {"max_output_tokens": 20000} const result = await tools.exec_command({ cmd: "python3 -c \"import sys; sys.stdout.write('x' * 50000)\"" }); -text(result.output); +text(`Variable truncated: ${result.output.length === 50000 ? "False" : "True"}. Variable: ${result.output}`); "#, + "gpt-5.4", |config| { config.tool_output_token_limit = Some(50); }, ) .await?; - assert_eq!( - text_item( - &custom_tool_output_items(&second_mock.single_request(), "call-1"), - /*index*/ 1 - ), - "x".repeat(50_000) + let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); + let output = text_item(&items, /*index*/ 1); + assert_regex_match( + r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", + output, ); Ok(()) From 29c1e73c561d3a5bf6f15aeb83d168e0c7105380 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 16 Jun 2026 22:30:53 +0000 Subject: [PATCH 3/6] Clarify code mode truncation tests --- codex-rs/core/tests/suite/code_mode.rs | 83 +++++++++++++++----------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index 5d57640ddf5f..4cf0dee12e28 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -940,9 +940,24 @@ text(JSON.stringify(results)); Ok(()) } +// This model uses token-based tool-output truncation, giving the downstream +// history assertions a stable `…N tokens truncated…` marker. +const TOKEN_POLICY_TEST_MODEL: &str = "gpt-5.4"; + +/// Confirms JavaScript observed an intact result before its printed value was +/// truncated while being recorded into conversation history. +fn assert_result_variable_preserved_before_history_truncation(output: &str) { + assert_regex_match( + r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", + output, + ); +} + +// A nested `exec_command` limit applies to `result.output` inside JavaScript. +// The outer code-mode and history budgets apply after the script calls `text`. #[cfg_attr(windows, ignore = "no exec_command on Windows")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn code_mode_exec_command_explicit_max_output_tokens_truncates() -> Result<()> { +async fn code_mode_exec_nested_limit_formats_truncated_result_with_warning() -> Result<()> { skip_if_no_network!(Ok(())); let server = responses::start_mock_server().await; @@ -972,7 +987,8 @@ text(result.output); #[cfg_attr(windows, ignore = "no exec_command on Windows")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn code_mode_exec_explicit_max_above_default_preserves_variable() -> Result<()> { +async fn code_mode_exec_nested_limit_preserves_result_variable_before_default_history_truncation() +-> Result<()> { // TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode. skip_if_wine_exec!( Ok(()), @@ -989,26 +1005,24 @@ const result = await tools.exec_command({ cmd: "python3 -c \"import sys; sys.stdout.write('x' * 50000)\"", max_output_tokens: 20000 }); -text(`Variable truncated: ${result.output.length === 50000 ? "False" : "True"}. Variable: ${result.output}`); +const resultVariableWasTruncated = result.output.length !== 50000; +text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Variable: ${result.output}`); "#, - "gpt-5.4", + TOKEN_POLICY_TEST_MODEL, |_| {}, ) .await?; let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); let output = text_item(&items, /*index*/ 1); - assert_regex_match( - r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", - output, - ); + assert_result_variable_preserved_before_history_truncation(output); Ok(()) } #[cfg_attr(windows, ignore = "no exec_command on Windows")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn code_mode_exec_explicit_max_above_default_truncates_larger_variable() -> Result<()> { +async fn code_mode_exec_nested_limit_truncates_result_variable_when_exceeded() -> Result<()> { // TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode. skip_if_wine_exec!( Ok(()), @@ -1025,16 +1039,18 @@ const result = await tools.exec_command({ cmd: "python3 -c \"import sys; sys.stdout.write('A' * 90000)\"", max_output_tokens: 20000 }); -const variableTruncated = result.output.includes("…2500 tokens truncated…"); -text(`Variable truncated: ${variableTruncated ? "True" : "False"}. Variable: ${result.output}`); +const resultVariableWasTruncated = result.output.includes("…2500 tokens truncated…"); +text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Variable: ${result.output}`); "#, - "gpt-5.4", + TOKEN_POLICY_TEST_MODEL, |_| {}, ) .await?; let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); let output = text_item(&items, /*index*/ 1); + // The boolean describes the nested result; the marker below comes from + // history truncating the value emitted with `text` afterward. assert_regex_match( r"(?s)^Variable truncated: True\. Variable: .*…\d+ tokens truncated…A+$", output, @@ -1045,7 +1061,8 @@ text(`Variable truncated: ${variableTruncated ? "True" : "False"}. Variable: ${r #[cfg_attr(windows, ignore = "no exec_command on Windows")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn code_mode_exec_explicit_max_above_truncation_policy_preserves_variable() -> Result<()> { +async fn code_mode_exec_nested_limit_preserves_result_variable_before_configured_history_truncation() +-> Result<()> { // TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode. skip_if_wine_exec!( Ok(()), @@ -1062,9 +1079,10 @@ const result = await tools.exec_command({ cmd: "python3 -c \"import sys; sys.stdout.write('x' * 50000)\"", max_output_tokens: 20000 }); -text(`Variable truncated: ${result.output.length === 50000 ? "False" : "True"}. Variable: ${result.output}`); +const resultVariableWasTruncated = result.output.length !== 50000; +text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Variable: ${result.output}`); "#, - "gpt-5.4", + TOKEN_POLICY_TEST_MODEL, |config| { config.tool_output_token_limit = Some(50); }, @@ -1073,17 +1091,15 @@ text(`Variable truncated: ${result.output.length === 50000 ? "False" : "True"}. let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); let output = text_item(&items, /*index*/ 1); - assert_regex_match( - r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", - output, - ); + assert_result_variable_preserved_before_history_truncation(output); Ok(()) } #[cfg_attr(windows, ignore = "no exec_command on Windows")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn code_mode_exec_without_max_preserves_variable_beyond_default() -> Result<()> { +async fn code_mode_exec_without_nested_limit_preserves_result_variable_before_default_history_truncation() +-> Result<()> { // TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode. skip_if_wine_exec!( Ok(()), @@ -1099,26 +1115,25 @@ async fn code_mode_exec_without_max_preserves_variable_beyond_default() -> Resul const result = await tools.exec_command({ cmd: "python3 -c \"import sys; sys.stdout.write('x' * 50000)\"" }); -text(`Variable truncated: ${result.output.length === 50000 ? "False" : "True"}. Variable: ${result.output}`); +const resultVariableWasTruncated = result.output.length !== 50000; +text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Variable: ${result.output}`); "#, - "gpt-5.4", + TOKEN_POLICY_TEST_MODEL, |_| {}, ) .await?; let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); let output = text_item(&items, /*index*/ 1); - assert_regex_match( - r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", - output, - ); + assert_result_variable_preserved_before_history_truncation(output); Ok(()) } #[cfg_attr(windows, ignore = "no exec_command on Windows")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn code_mode_exec_without_max_preserves_variable_beyond_truncation_policy() -> Result<()> { +async fn code_mode_exec_without_nested_limit_preserves_result_variable_before_configured_history_truncation() +-> Result<()> { // TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode. skip_if_wine_exec!( Ok(()), @@ -1134,9 +1149,10 @@ async fn code_mode_exec_without_max_preserves_variable_beyond_truncation_policy( const result = await tools.exec_command({ cmd: "python3 -c \"import sys; sys.stdout.write('x' * 50000)\"" }); -text(`Variable truncated: ${result.output.length === 50000 ? "False" : "True"}. Variable: ${result.output}`); +const resultVariableWasTruncated = result.output.length !== 50000; +text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Variable: ${result.output}`); "#, - "gpt-5.4", + TOKEN_POLICY_TEST_MODEL, |config| { config.tool_output_token_limit = Some(50); }, @@ -1145,17 +1161,16 @@ text(`Variable truncated: ${result.output.length === 50000 ? "False" : "True"}. let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); let output = text_item(&items, /*index*/ 1); - assert_regex_match( - r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", - output, - ); + assert_result_variable_preserved_before_history_truncation(output); Ok(()) } +// The outer directive limits output after JavaScript emits it; it does not +// limit `result.output` returned by the nested command. #[cfg_attr(windows, ignore = "no exec_command on Windows")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn code_mode_exec_explicit_max_output_tokens_truncates() -> Result<()> { +async fn code_mode_exec_outer_limit_truncates_emitted_output() -> Result<()> { skip_if_no_network!(Ok(())); let server = responses::start_mock_server().await; From 0270bc4f53a419a0f6a7761b3d664a34aca2620f Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 16 Jun 2026 22:34:10 +0000 Subject: [PATCH 4/6] codex: address PR review feedback (#28471) --- codex-rs/core/tests/suite/code_mode.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index 4cf0dee12e28..9a183a0f0fc3 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -1049,6 +1049,13 @@ text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Vari let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); let output = text_item(&items, /*index*/ 1); + // The nested 20,000-token budget leaves about 80,000 characters. This + // ceiling independently proves that history applied its smaller cap. + assert!( + output.len() < 60_000, + "expected history to truncate the emitted value, got {} bytes", + output.len() + ); // The boolean describes the nested result; the marker below comes from // history truncating the value emitted with `text` afterward. assert_regex_match( From 29dcd3f3402d7ffae92a4d1d783ae859eb248f0e Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 16 Jun 2026 22:44:49 +0000 Subject: [PATCH 5/6] codex: address configured cap review (#28471) --- codex-rs/core/tests/suite/code_mode.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index 9a183a0f0fc3..905ce900ef70 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -1082,11 +1082,13 @@ async fn code_mode_exec_nested_limit_preserves_result_variable_before_configured &server, "use exec_command from code mode", r#"// @exec: {"max_output_tokens": 20000} +// This value stays below the default history cap but exceeds the configured +// 50-token cap. const result = await tools.exec_command({ - cmd: "python3 -c \"import sys; sys.stdout.write('x' * 50000)\"", + cmd: "python3 -c \"import sys; sys.stdout.write('x' * 2000)\"", max_output_tokens: 20000 }); -const resultVariableWasTruncated = result.output.length !== 50000; +const resultVariableWasTruncated = result.output.length !== 2000; text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Variable: ${result.output}`); "#, TOKEN_POLICY_TEST_MODEL, @@ -1153,10 +1155,12 @@ async fn code_mode_exec_without_nested_limit_preserves_result_variable_before_co &server, "use exec_command from code mode", r#"// @exec: {"max_output_tokens": 20000} +// This value stays below the default history cap but exceeds the configured +// 50-token cap. const result = await tools.exec_command({ - cmd: "python3 -c \"import sys; sys.stdout.write('x' * 50000)\"" + cmd: "python3 -c \"import sys; sys.stdout.write('x' * 2000)\"" }); -const resultVariableWasTruncated = result.output.length !== 50000; +const resultVariableWasTruncated = result.output.length !== 2000; text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Variable: ${result.output}`); "#, TOKEN_POLICY_TEST_MODEL, From 289bbe6ede6e22c2470621a60e3db0a5797f0624 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 16 Jun 2026 23:24:02 +0000 Subject: [PATCH 6/6] codex: clarify configured truncation assertions (#28471) --- codex-rs/core/tests/suite/code_mode.rs | 55 ++++++++++++++++---------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index 905ce900ef70..68b0f999fa4b 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -944,15 +944,6 @@ text(JSON.stringify(results)); // history assertions a stable `…N tokens truncated…` marker. const TOKEN_POLICY_TEST_MODEL: &str = "gpt-5.4"; -/// Confirms JavaScript observed an intact result before its printed value was -/// truncated while being recorded into conversation history. -fn assert_result_variable_preserved_before_history_truncation(output: &str) { - assert_regex_match( - r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", - output, - ); -} - // A nested `exec_command` limit applies to `result.output` inside JavaScript. // The outer code-mode and history budgets apply after the script calls `text`. #[cfg_attr(windows, ignore = "no exec_command on Windows")] @@ -1015,7 +1006,10 @@ text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Vari let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); let output = text_item(&items, /*index*/ 1); - assert_result_variable_preserved_before_history_truncation(output); + assert_regex_match( + r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", + output, + ); Ok(()) } @@ -1082,13 +1076,11 @@ async fn code_mode_exec_nested_limit_preserves_result_variable_before_configured &server, "use exec_command from code mode", r#"// @exec: {"max_output_tokens": 20000} -// This value stays below the default history cap but exceeds the configured -// 50-token cap. const result = await tools.exec_command({ - cmd: "python3 -c \"import sys; sys.stdout.write('x' * 2000)\"", + cmd: "python3 -c \"import sys; sys.stdout.write('x' * 50000)\"", max_output_tokens: 20000 }); -const resultVariableWasTruncated = result.output.length !== 2000; +const resultVariableWasTruncated = result.output.length !== 50000; text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Variable: ${result.output}`); "#, TOKEN_POLICY_TEST_MODEL, @@ -1100,7 +1092,17 @@ text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Vari let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); let output = text_item(&items, /*index*/ 1); - assert_result_variable_preserved_before_history_truncation(output); + // The 50-token override must shrink this 50,000-character value far below + // what the default 10,000-token history cap would retain. + assert!( + output.len() < 1_000, + "expected configured history cap to truncate the emitted value, got {} bytes", + output.len() + ); + assert_regex_match( + r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", + output, + ); Ok(()) } @@ -1134,7 +1136,10 @@ text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Vari let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); let output = text_item(&items, /*index*/ 1); - assert_result_variable_preserved_before_history_truncation(output); + assert_regex_match( + r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", + output, + ); Ok(()) } @@ -1155,12 +1160,10 @@ async fn code_mode_exec_without_nested_limit_preserves_result_variable_before_co &server, "use exec_command from code mode", r#"// @exec: {"max_output_tokens": 20000} -// This value stays below the default history cap but exceeds the configured -// 50-token cap. const result = await tools.exec_command({ - cmd: "python3 -c \"import sys; sys.stdout.write('x' * 2000)\"" + cmd: "python3 -c \"import sys; sys.stdout.write('x' * 50000)\"" }); -const resultVariableWasTruncated = result.output.length !== 2000; +const resultVariableWasTruncated = result.output.length !== 50000; text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Variable: ${result.output}`); "#, TOKEN_POLICY_TEST_MODEL, @@ -1172,7 +1175,17 @@ text(`Variable truncated: ${resultVariableWasTruncated ? "True" : "False"}. Vari let items = custom_tool_output_items(&second_mock.single_request(), "call-1"); let output = text_item(&items, /*index*/ 1); - assert_result_variable_preserved_before_history_truncation(output); + // The 50-token override must shrink this 50,000-character value far below + // what the default 10,000-token history cap would retain. + assert!( + output.len() < 1_000, + "expected configured history cap to truncate the emitted value, got {} bytes", + output.len() + ); + assert_regex_match( + r"^Variable truncated: False\. Variable: x+…\d+ tokens truncated…x+$", + output, + ); Ok(()) }