[codex] Preserve raw code-mode exec output by default (#23564)

## Why
Code mode can use nested unified exec calls as data sources. When those
calls omit `max_output_tokens`, code mode should receive raw command
output so the script can parse or summarize it itself. When code mode
does provide `max_output_tokens`, that explicit nested budget should be
respected, including values above the default unified exec limit, rather
than being capped before code mode sees the result.

## What
- Preserve direct unified exec truncation behavior, while letting
code-mode exec/write_stdin keep `max_output_tokens` as `None` unless
explicitly supplied.
- Make code-mode tool results use raw output when no explicit limit is
present, and use the explicit nested limit directly when one is
specified.
- Refactor unified exec output formatting so `truncated_output` takes
the caller-selected token budget.
- Add e2e integration coverage for explicit nested exec limits, omitted
nested exec limits, outer exec limit propagation, omitted-limit outputs
that exceed both the default and a small truncation policy, explicit
nested limits above those caps, and high explicit limits that still
compact larger command output.
- Reuse the code-mode turn setup helper while directly asserting the
exact exec output item in each test.

## Testing
- `just fmt`
- `git diff --check`
- Not run locally per repo guidance; CI should validate the e2e
integration tests.
This commit is contained in:
Ahmed Ibrahim
2026-05-20 04:02:14 +00:00
committed by GitHub
parent e43a2e297f
commit 5a4202ad90
10 changed files with 278 additions and 57 deletions
+2
View File
@@ -31,6 +31,7 @@ use codex_exec_server::Environment;
use codex_network_proxy::NetworkProxy;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_output_truncation::TruncationPolicy;
use rand::Rng;
use rand::rng;
use tokio::sync::Mutex;
@@ -111,6 +112,7 @@ pub(crate) struct WriteStdinRequest<'a> {
pub input: &'a str,
pub yield_time_ms: u64,
pub max_output_tokens: Option<usize>,
pub truncation_policy: TruncationPolicy,
}
#[derive(Default)]
+31 -8
View File
@@ -10,6 +10,7 @@ use crate::tools::context::ExecCommandToolOutput;
use crate::unified_exec::WriteStdinRequest;
use crate::unified_exec::process::OutputHandles;
use codex_sandboxing::SandboxType;
use codex_utils_output_truncation::TruncationPolicy;
use codex_utils_output_truncation::approx_token_count;
use core_test_support::get_remote_test_env;
use core_test_support::skip_if_sandbox;
@@ -162,6 +163,7 @@ async fn exec_command_with_tty(
chunk_id: generate_chunk_id(),
wall_time,
raw_output: collected,
truncation_policy: turn.truncation_policy,
max_output_tokens: None,
process_id: response_process_id,
exit_code,
@@ -195,6 +197,7 @@ async fn write_stdin(
input,
yield_time_ms,
max_output_tokens: None,
truncation_policy: TruncationPolicy::Tokens(10_000),
})
.await
}
@@ -260,7 +263,9 @@ async fn unified_exec_persists_across_requests() -> anyhow::Result<()> {
)
.await?;
assert!(
out_2.truncated_output().contains("codex"),
out_2
.truncated_output(DEFAULT_MAX_OUTPUT_TOKENS)
.contains("codex"),
"expected environment variable output"
);
@@ -301,7 +306,9 @@ async fn multi_unified_exec_sessions() -> anyhow::Result<()> {
"short command should not report a process id if it exits quickly"
);
assert!(
!out_2.truncated_output().contains("codex"),
!out_2
.truncated_output(DEFAULT_MAX_OUTPUT_TOKENS)
.contains("codex"),
"short command should run in a fresh shell"
);
@@ -313,7 +320,9 @@ async fn multi_unified_exec_sessions() -> anyhow::Result<()> {
)
.await?;
assert!(
out_3.truncated_output().contains("codex"),
out_3
.truncated_output(DEFAULT_MAX_OUTPUT_TOKENS)
.contains("codex"),
"session should preserve state"
);
@@ -350,7 +359,9 @@ async fn unified_exec_timeouts() -> anyhow::Result<()> {
)
.await?;
assert!(
!out_2.truncated_output().contains(TEST_VAR_VALUE),
!out_2
.truncated_output(DEFAULT_MAX_OUTPUT_TOKENS)
.contains(TEST_VAR_VALUE),
"timeout too short should yield incomplete output"
);
@@ -359,7 +370,9 @@ async fn unified_exec_timeouts() -> anyhow::Result<()> {
let out_3 = write_stdin(&session, process_id, "", /*yield_time_ms*/ 100).await?;
assert!(
out_3.truncated_output().contains(TEST_VAR_VALUE),
out_3
.truncated_output(DEFAULT_MAX_OUTPUT_TOKENS)
.contains(TEST_VAR_VALUE),
"subsequent poll should retrieve output"
);
@@ -394,7 +407,9 @@ async fn unified_exec_pause_blocks_yield_timeout() -> anyhow::Result<()> {
"pause should block the unified exec yield timeout"
);
assert!(
response.truncated_output().contains("unified-exec-done"),
response
.truncated_output(DEFAULT_MAX_OUTPUT_TOKENS)
.contains("unified-exec-done"),
"exec_command should wait for output after the pause lifts"
);
assert!(
@@ -420,7 +435,11 @@ async fn requests_with_large_timeout_are_capped() -> anyhow::Result<()> {
.await?;
assert!(result.process_id.is_some());
assert!(result.truncated_output().contains("codex"));
assert!(
result
.truncated_output(DEFAULT_MAX_OUTPUT_TOKENS)
.contains("codex")
);
Ok(())
}
@@ -442,7 +461,11 @@ async fn completed_commands_do_not_persist_sessions() -> anyhow::Result<()> {
result.process_id.is_some(),
"completed command should report a process id"
);
assert!(result.truncated_output().contains("codex"));
assert!(
result
.truncated_output(DEFAULT_MAX_OUTPUT_TOKENS)
.contains("codex")
);
assert!(
session
@@ -581,6 +581,7 @@ impl UnifiedExecProcessManager {
chunk_id,
wall_time,
raw_output: collected,
truncation_policy: context.turn.truncation_policy,
max_output_tokens: request.max_output_tokens,
process_id: response_process_id,
exit_code,
@@ -725,6 +726,7 @@ impl UnifiedExecProcessManager {
chunk_id,
wall_time,
raw_output: collected,
truncation_policy: request.truncation_policy,
max_output_tokens: request.max_output_tokens,
process_id,
exit_code,