From f7d9e64b2faec589aad2380ffef15dc993a2311c Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 3 Oct 2025 18:57:27 -0700 Subject: [PATCH 01/12] exec-bug --- codex-rs/core/src/tools/mod.rs | 65 +++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/codex-rs/core/src/tools/mod.rs b/codex-rs/core/src/tools/mod.rs index 5a120d0907..f479595c86 100644 --- a/codex-rs/core/src/tools/mod.rs +++ b/codex-rs/core/src/tools/mod.rs @@ -159,9 +159,12 @@ pub(crate) async fn handle_container_exec_with_params( Err(ExecError::Codex(CodexErr::Sandbox(SandboxErr::Timeout { output }))) => Err( FunctionCallError::RespondToModel(format_exec_output_apply_patch(&output)), ), - Err(ExecError::Codex(err)) => Err(FunctionCallError::RespondToModel(format!( - "execution error: {err:?}" - ))), + Err(ExecError::Codex(err)) => { + let message = format!("execution error: {err:?}"); + Err(FunctionCallError::RespondToModel( + truncate_formatted_exec_output(&message), + )) + } } } @@ -206,26 +209,28 @@ pub fn format_exec_output_str(exec_output: &ExecToolCallOutput) -> String { aggregated_output, .. } = exec_output; - // Head+tail truncation for the model: show the beginning and end with an elision. - // Clients still receive full streams; only this formatted summary is capped. - - let mut s = &aggregated_output.text; - let prefixed_str: String; + let content = aggregated_output.text.as_str(); if exec_output.timed_out { - prefixed_str = format!( - "command timed out after {} milliseconds\n", + let prefixed = format!( + "command timed out after {} milliseconds\n{content}", exec_output.duration.as_millis() - ) + s; - s = &prefixed_str; + ); + return truncate_formatted_exec_output(&prefixed); } - let total_lines = s.lines().count(); - if s.len() <= MODEL_FORMAT_MAX_BYTES && total_lines <= MODEL_FORMAT_MAX_LINES { - return s.to_string(); + truncate_formatted_exec_output(content) +} + +fn truncate_formatted_exec_output(content: &str) -> String { + // Head+tail truncation for the model: show the beginning and end with an elision. + // Clients still receive full streams; only this formatted summary is capped. + let total_lines = content.lines().count(); + if content.len() <= MODEL_FORMAT_MAX_BYTES && total_lines <= MODEL_FORMAT_MAX_LINES { + return content.to_string(); } - let segments: Vec<&str> = s.split_inclusive('\n').collect(); + let segments: Vec<&str> = content.split_inclusive('\n').collect(); let head_take = MODEL_FORMAT_HEAD_LINES.min(segments.len()); let tail_take = MODEL_FORMAT_TAIL_LINES.min(segments.len().saturating_sub(head_take)); let omitted = segments.len().saturating_sub(head_take + tail_take); @@ -236,9 +241,9 @@ pub fn format_exec_output_str(exec_output: &ExecToolCallOutput) -> String { .map(|segment| segment.len()) .sum(); let tail_slice_start: usize = if tail_take == 0 { - s.len() + content.len() } else { - s.len() + content.len() - segments .iter() .rev() @@ -260,9 +265,9 @@ pub fn format_exec_output_str(exec_output: &ExecToolCallOutput) -> String { head_budget = MODEL_FORMAT_MAX_BYTES.saturating_sub(marker.len()); } - let head_slice = &s[..head_slice_end]; + let head_slice = &content[..head_slice_end]; let head_part = take_bytes_at_char_boundary(head_slice, head_budget); - let mut result = String::with_capacity(MODEL_FORMAT_MAX_BYTES.min(s.len())); + let mut result = String::with_capacity(MODEL_FORMAT_MAX_BYTES.min(content.len())); result.push_str(head_part); result.push_str(&marker); @@ -272,9 +277,27 @@ pub fn format_exec_output_str(exec_output: &ExecToolCallOutput) -> String { return result; } - let tail_slice = &s[tail_slice_start..]; + let tail_slice = &content[tail_slice_start..]; let tail_part = take_last_bytes_at_char_boundary(tail_slice, remaining); result.push_str(tail_part); result } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn truncate_formatted_exec_output_truncates_large_error() { + let line = "very long execution error line that should trigger truncation\n"; + let large_error = line.repeat(2_500); // way beyond both byte and line limits + + let truncated = truncate_formatted_exec_output(&large_error); + + assert!(truncated.len() <= MODEL_FORMAT_MAX_BYTES); + assert!(truncated.starts_with(line)); + assert!(truncated.contains("[... omitted")); + assert_ne!(truncated, large_error); + } +} From bf4d5a926fcafbbc43cec2040fb2061b2d37f1a7 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 3 Oct 2025 19:01:51 -0700 Subject: [PATCH 02/12] exec-bug --- codex-rs/core/tests/suite/tools.rs | 68 ++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/codex-rs/core/tests/suite/tools.rs b/codex-rs/core/tests/suite/tools.rs index 4d6ccdac0c..5d31fb43fd 100644 --- a/codex-rs/core/tests/suite/tools.rs +++ b/codex-rs/core/tests/suite/tools.rs @@ -448,3 +448,71 @@ async fn shell_timeout_includes_timeout_prefix_and_metadata() -> Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let mut builder = test_codex(); + let test = builder.build(&server).await?; + + let call_id = "shell-denied"; + let long_line = "this is a long stdout line that should trigger truncation 0123456789abcdefghijklmnopqrstuvwxyz"; + let script = format!( + "for i in $(seq 1 500); do echo '{long_line}'; done; cat <<'EOF' > denied.txt\ncontent\nEOF", + ); + let args = json!({ + "command": ["/bin/sh", "-c", script], + "timeout_ms": 1_000, + }); + + let responses = vec![ + sse(vec![ + json!({"type": "response.created", "response": {"id": "resp-1"}}), + ev_function_call(call_id, "shell", &serde_json::to_string(&args)?), + ev_completed("resp-1"), + ]), + sse(vec![ + ev_assistant_message("msg-1", "done"), + ev_completed("resp-2"), + ]), + ]; + mount_sse_sequence(&server, responses).await; + + submit_turn( + &test, + "attempt to write in read-only sandbox", + AskForApproval::Never, + SandboxPolicy::ReadOnly, + ) + .await?; + + let requests = server.received_requests().await.expect("recorded requests"); + let bodies = request_bodies(&requests)?; + let function_outputs = collect_output_items(&bodies, "function_call_output"); + let denied_item = function_outputs + .iter() + .find(|item| item.get("call_id").and_then(Value::as_str) == Some(call_id)) + .expect("denied output present"); + + let output = denied_item + .get("output") + .and_then(Value::as_str) + .expect("denied output string"); + + assert!( + output.starts_with("execution error:"), + "expected execution error prefix, got {output:?}" + ); + assert!( + output.contains("[... omitted"), + "expected truncated marker, got {output:?}" + ); + assert!( + output.contains(long_line), + "expected truncated output to retain sample of stdout, got {output:?}" + ); + + Ok(()) +} From 0fbe4bfafa416bd4e9e51ecc882c59ed89bf2007 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 3 Oct 2025 19:11:17 -0700 Subject: [PATCH 03/12] exec-bug --- codex-rs/core/src/tools/mod.rs | 14 +++++++++++++- codex-rs/core/tests/suite/tools.rs | 13 +++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/codex-rs/core/src/tools/mod.rs b/codex-rs/core/src/tools/mod.rs index f479595c86..16548abeec 100644 --- a/codex-rs/core/src/tools/mod.rs +++ b/codex-rs/core/src/tools/mod.rs @@ -155,7 +155,19 @@ pub(crate) async fn handle_container_exec_with_params( Err(FunctionCallError::RespondToModel(content)) } } - Err(ExecError::Function(err)) => Err(err), + Err(ExecError::Function(err)) => match err { + FunctionCallError::RespondToModel(msg) => { + let message = truncate_formatted_exec_output(&msg); + Err(FunctionCallError::RespondToModel(message)) + } + FunctionCallError::MissingLocalShellCallId => { + Err(FunctionCallError::MissingLocalShellCallId) + } + FunctionCallError::Fatal(msg) => { + let message = truncate_formatted_exec_output(&msg); + Err(FunctionCallError::Fatal(message)) + } + }, Err(ExecError::Codex(CodexErr::Sandbox(SandboxErr::Timeout { output }))) => Err( FunctionCallError::RespondToModel(format_exec_output_apply_patch(&output)), ), diff --git a/codex-rs/core/tests/suite/tools.rs b/codex-rs/core/tests/suite/tools.rs index 5d31fb43fd..c8cd120c37 100644 --- a/codex-rs/core/tests/suite/tools.rs +++ b/codex-rs/core/tests/suite/tools.rs @@ -458,9 +458,9 @@ async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { let test = builder.build(&server).await?; let call_id = "shell-denied"; - let long_line = "this is a long stdout line that should trigger truncation 0123456789abcdefghijklmnopqrstuvwxyz"; + let long_line = "this is a long stderr line that should trigger truncation 0123456789abcdefghijklmnopqrstuvwxyz"; let script = format!( - "for i in $(seq 1 500); do echo '{long_line}'; done; cat <<'EOF' > denied.txt\ncontent\nEOF", + "for i in $(seq 1 500); do echo '{long_line}' >&2; done; cat <<'EOF' > denied.txt\ncontent\nEOF", ); let args = json!({ "command": ["/bin/sh", "-c", script], @@ -502,8 +502,13 @@ async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { .expect("denied output string"); assert!( - output.starts_with("execution error:"), - "expected execution error prefix, got {output:?}" + output.starts_with("failed in sandbox:"), + "expected sandbox failure prefix, got {output:?}" + ); + assert!( + output.len() <= 10 * 1024, + "expected truncated output to stay within byte budget, len={}", + output.len() ); assert!( output.contains("[... omitted"), From 8598cae87841b0a95efe2119d4e3bc4f92be55f8 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 3 Oct 2025 19:15:47 -0700 Subject: [PATCH 04/12] exec-bug --- codex-rs/core/tests/suite/tools.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/codex-rs/core/tests/suite/tools.rs b/codex-rs/core/tests/suite/tools.rs index c8cd120c37..5d31fb43fd 100644 --- a/codex-rs/core/tests/suite/tools.rs +++ b/codex-rs/core/tests/suite/tools.rs @@ -458,9 +458,9 @@ async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { let test = builder.build(&server).await?; let call_id = "shell-denied"; - let long_line = "this is a long stderr line that should trigger truncation 0123456789abcdefghijklmnopqrstuvwxyz"; + let long_line = "this is a long stdout line that should trigger truncation 0123456789abcdefghijklmnopqrstuvwxyz"; let script = format!( - "for i in $(seq 1 500); do echo '{long_line}' >&2; done; cat <<'EOF' > denied.txt\ncontent\nEOF", + "for i in $(seq 1 500); do echo '{long_line}'; done; cat <<'EOF' > denied.txt\ncontent\nEOF", ); let args = json!({ "command": ["/bin/sh", "-c", script], @@ -502,13 +502,8 @@ async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { .expect("denied output string"); assert!( - output.starts_with("failed in sandbox:"), - "expected sandbox failure prefix, got {output:?}" - ); - assert!( - output.len() <= 10 * 1024, - "expected truncated output to stay within byte budget, len={}", - output.len() + output.starts_with("execution error:"), + "expected execution error prefix, got {output:?}" ); assert!( output.contains("[... omitted"), From 1de6631b0f1e6ef3a7e61a8f291d36ee4252cadc Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 3 Oct 2025 19:17:16 -0700 Subject: [PATCH 05/12] exec-bug --- codex-rs/core/src/tools/mod.rs | 58 ++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/codex-rs/core/src/tools/mod.rs b/codex-rs/core/src/tools/mod.rs index 16548abeec..b266a812c1 100644 --- a/codex-rs/core/src/tools/mod.rs +++ b/codex-rs/core/src/tools/mod.rs @@ -155,19 +155,7 @@ pub(crate) async fn handle_container_exec_with_params( Err(FunctionCallError::RespondToModel(content)) } } - Err(ExecError::Function(err)) => match err { - FunctionCallError::RespondToModel(msg) => { - let message = truncate_formatted_exec_output(&msg); - Err(FunctionCallError::RespondToModel(message)) - } - FunctionCallError::MissingLocalShellCallId => { - Err(FunctionCallError::MissingLocalShellCallId) - } - FunctionCallError::Fatal(msg) => { - let message = truncate_formatted_exec_output(&msg); - Err(FunctionCallError::Fatal(message)) - } - }, + Err(ExecError::Function(err)) => Err(truncate_function_error(err)), Err(ExecError::Codex(CodexErr::Sandbox(SandboxErr::Timeout { output }))) => Err( FunctionCallError::RespondToModel(format_exec_output_apply_patch(&output)), ), @@ -296,6 +284,18 @@ fn truncate_formatted_exec_output(content: &str) -> String { result } +fn truncate_function_error(err: FunctionCallError) -> FunctionCallError { + match err { + FunctionCallError::RespondToModel(msg) => { + FunctionCallError::RespondToModel(truncate_formatted_exec_output(&msg)) + } + FunctionCallError::Fatal(msg) => { + FunctionCallError::Fatal(truncate_formatted_exec_output(&msg)) + } + other => other, + } +} + #[cfg(test)] mod tests { use super::*; @@ -312,4 +312,36 @@ mod tests { assert!(truncated.contains("[... omitted")); assert_ne!(truncated, large_error); } + + #[test] + fn truncate_function_error_trims_respond_to_model() { + let line = "respond-to-model error that should be truncated\n"; + let huge = line.repeat(3_000); + + let err = truncate_function_error(FunctionCallError::RespondToModel(huge.clone())); + match err { + FunctionCallError::RespondToModel(message) => { + assert!(message.len() <= MODEL_FORMAT_MAX_BYTES); + assert!(message.contains("[... omitted")); + assert!(message.starts_with(line)); + } + other => panic!("unexpected error variant: {other:?}"), + } + } + + #[test] + fn truncate_function_error_trims_fatal() { + let line = "fatal error output that should be truncated\n"; + let huge = line.repeat(3_000); + + let err = truncate_function_error(FunctionCallError::Fatal(huge.clone())); + match err { + FunctionCallError::Fatal(message) => { + assert!(message.len() <= MODEL_FORMAT_MAX_BYTES); + assert!(message.contains("[... omitted")); + assert!(message.starts_with(line)); + } + other => panic!("unexpected error variant: {other:?}"), + } + } } From 0054d004ace49469a4635b0b2a3a43d486ef6eb9 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 3 Oct 2025 20:03:12 -0700 Subject: [PATCH 06/12] exec-bug --- codex-rs/core/tests/suite/tools.rs | 81 ++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/codex-rs/core/tests/suite/tools.rs b/codex-rs/core/tests/suite/tools.rs index 5d31fb43fd..f149a50f79 100644 --- a/codex-rs/core/tests/suite/tools.rs +++ b/codex-rs/core/tests/suite/tools.rs @@ -458,9 +458,9 @@ async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { let test = builder.build(&server).await?; let call_id = "shell-denied"; - let long_line = "this is a long stdout line that should trigger truncation 0123456789abcdefghijklmnopqrstuvwxyz"; + let long_line = "this is a long stderr line that should trigger truncation 0123456789abcdefghijklmnopqrstuvwxyz"; let script = format!( - "for i in $(seq 1 500); do echo '{long_line}'; done; cat <<'EOF' > denied.txt\ncontent\nEOF", + "for i in $(seq 1 500); do >&2 echo '{long_line}'; done; cat <<'EOF' > denied.txt\ncontent\nEOF", ); let args = json!({ "command": ["/bin/sh", "-c", script], @@ -502,8 +502,8 @@ async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { .expect("denied output string"); assert!( - output.starts_with("execution error:"), - "expected execution error prefix, got {output:?}" + output.starts_with("failed in sandbox:"), + "expected sandbox failure prefix, got {output:?}" ); assert!( output.contains("[... omitted"), @@ -511,8 +511,79 @@ async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { ); assert!( output.contains(long_line), - "expected truncated output to retain sample of stdout, got {output:?}" + "expected truncated stderr sample, got {output:?}" + ); + assert!( + output.contains("Operation not permitted"), + "expected sandbox error detail, got {output:?}" + ); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn shell_spawn_failure_truncates_exec_error() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let mut builder = test_codex().with_config(|cfg| { + cfg.sandbox_policy = SandboxPolicy::DangerFullAccess; + }); + let test = builder.build(&server).await?; + + let call_id = "shell-spawn-failure"; + let bogus_component = "missing-bin-".repeat(700); + let bogus_exe = test + .cwd + .path() + .join(bogus_component) + .to_string_lossy() + .into_owned(); + + let args = json!({ + "command": [bogus_exe], + "timeout_ms": 1_000, + }); + + let responses = vec![ + sse(vec![ + json!({"type": "response.created", "response": {"id": "resp-1"}}), + ev_function_call(call_id, "shell", &serde_json::to_string(&args)?), + ev_completed("resp-1"), + ]), + sse(vec![ + ev_assistant_message("msg-1", "done"), + ev_completed("resp-2"), + ]), + ]; + mount_sse_sequence(&server, responses).await; + + submit_turn( + &test, + "spawn a missing binary", + AskForApproval::Never, + SandboxPolicy::DangerFullAccess, + ) + .await?; + + let requests = server.received_requests().await.expect("recorded requests"); + let bodies = request_bodies(&requests)?; + let function_outputs = collect_output_items(&bodies, "function_call_output"); + let failure_item = function_outputs + .iter() + .find(|item| item.get("call_id").and_then(Value::as_str) == Some(call_id)) + .expect("spawn failure output present"); + + let output = failure_item + .get("output") + .and_then(Value::as_str) + .expect("spawn failure output string"); + + assert!( + output.starts_with("execution error:"), + "expected execution error prefix, got {output:?}" ); + assert!(output.len() <= 10 * 1024); Ok(()) } From c40231258d1f188b4c6b4def760c1c4503a401e6 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 3 Oct 2025 20:08:13 -0700 Subject: [PATCH 07/12] exec-bug --- codex-rs/core/tests/suite/tools.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/codex-rs/core/tests/suite/tools.rs b/codex-rs/core/tests/suite/tools.rs index f149a50f79..5c0ca93197 100644 --- a/codex-rs/core/tests/suite/tools.rs +++ b/codex-rs/core/tests/suite/tools.rs @@ -502,8 +502,8 @@ async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { .expect("denied output string"); assert!( - output.starts_with("failed in sandbox:"), - "expected sandbox failure prefix, got {output:?}" + output.starts_with("execution error:"), + "expected execution error prefix, got {output:?}" ); assert!( output.contains("[... omitted"), @@ -513,10 +513,7 @@ async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { output.contains(long_line), "expected truncated stderr sample, got {output:?}" ); - assert!( - output.contains("Operation not permitted"), - "expected sandbox error detail, got {output:?}" - ); + assert!(output.contains("Operation not permitted")); Ok(()) } From 1b13899caeb566eca7a97945bcacbf544bd1f69d Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 3 Oct 2025 20:16:51 -0700 Subject: [PATCH 08/12] exec-bug --- codex-rs/core/src/tools/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/tools/mod.rs b/codex-rs/core/src/tools/mod.rs index b266a812c1..a0b0c58d97 100644 --- a/codex-rs/core/src/tools/mod.rs +++ b/codex-rs/core/src/tools/mod.rs @@ -318,7 +318,7 @@ mod tests { let line = "respond-to-model error that should be truncated\n"; let huge = line.repeat(3_000); - let err = truncate_function_error(FunctionCallError::RespondToModel(huge.clone())); + let err = truncate_function_error(FunctionCallError::RespondToModel(huge)); match err { FunctionCallError::RespondToModel(message) => { assert!(message.len() <= MODEL_FORMAT_MAX_BYTES); @@ -334,7 +334,7 @@ mod tests { let line = "fatal error output that should be truncated\n"; let huge = line.repeat(3_000); - let err = truncate_function_error(FunctionCallError::Fatal(huge.clone())); + let err = truncate_function_error(FunctionCallError::Fatal(huge)); match err { FunctionCallError::Fatal(message) => { assert!(message.len() <= MODEL_FORMAT_MAX_BYTES); From 9aa15ef80b393573d984d72691c47c89f9240625 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 3 Oct 2025 20:54:12 -0700 Subject: [PATCH 09/12] ctx --- codex-rs/core/tests/suite/tools.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex-rs/core/tests/suite/tools.rs b/codex-rs/core/tests/suite/tools.rs index 5c0ca93197..2b0da6d05c 100644 --- a/codex-rs/core/tests/suite/tools.rs +++ b/codex-rs/core/tests/suite/tools.rs @@ -502,7 +502,7 @@ async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { .expect("denied output string"); assert!( - output.starts_with("execution error:"), + output.starts_with("sandbox error:"), "expected execution error prefix, got {output:?}" ); assert!( From 8d8af8385a76442924e653df57dd989c54364f74 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 3 Oct 2025 21:05:24 -0700 Subject: [PATCH 10/12] ctx --- codex-rs/core/tests/suite/tools.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/tests/suite/tools.rs b/codex-rs/core/tests/suite/tools.rs index 2b0da6d05c..0fe1581660 100644 --- a/codex-rs/core/tests/suite/tools.rs +++ b/codex-rs/core/tests/suite/tools.rs @@ -502,8 +502,8 @@ async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { .expect("denied output string"); assert!( - output.starts_with("sandbox error:"), - "expected execution error prefix, got {output:?}" + output.starts_with("failed in sandbox: "), + "expected sandbox error prefix, got {output:?}" ); assert!( output.contains("[... omitted"), From aaffc15a140ebcd99ee209a8972635dcf4451813 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 3 Oct 2025 21:21:05 -0700 Subject: [PATCH 11/12] ctx --- codex-rs/core/tests/suite/tools.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/tests/suite/tools.rs b/codex-rs/core/tests/suite/tools.rs index 0fe1581660..ba4a88339b 100644 --- a/codex-rs/core/tests/suite/tools.rs +++ b/codex-rs/core/tests/suite/tools.rs @@ -513,7 +513,18 @@ async fn shell_sandbox_denied_truncates_error_output() -> Result<()> { output.contains(long_line), "expected truncated stderr sample, got {output:?}" ); - assert!(output.contains("Operation not permitted")); + // Linux distributions may surface sandbox write failures as different errno messages + // depending on the underlying mechanism (e.g., EPERM, EACCES, or EROFS). Accept a + // small set of common variants to keep this cross-platform. + let denial_markers = [ + "Operation not permitted", // EPERM + "Permission denied", // EACCES + "Read-only file system", // EROFS + ]; + assert!( + denial_markers.iter().any(|m| output.contains(m)), + "expected sandbox denial message, got {output:?}" + ); Ok(()) } From 72888d7080dd80e9443248f294349565e9f61bb4 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 3 Oct 2025 21:22:19 -0700 Subject: [PATCH 12/12] ctx --- codex-rs/core/src/tools/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/codex-rs/core/src/tools/mod.rs b/codex-rs/core/src/tools/mod.rs index a0b0c58d97..d0c922d100 100644 --- a/codex-rs/core/src/tools/mod.rs +++ b/codex-rs/core/src/tools/mod.rs @@ -145,6 +145,7 @@ pub(crate) async fn handle_container_exec_with_params( ) .await; + // always make sure to truncate the output if its length isn't controlled. match output_result { Ok(output) => { let ExecToolCallOutput { exit_code, .. } = &output;