You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[code-mode] exec results honour the script's max_output_tokens with no upper bound — one 497 KB shell result reached the model as ~138K tokens (function mode clamps to the model's truncation policy) #42367
codex-cli 0.137.0-alpha.4 (app-server, driven through the openai-codex Python SDK 0.1.0b3). The same code is on main today.
Which model were you using?
gpt-5.6-luna (Azure OpenAI deployment; the catalog resolves it to tool_mode: code_mode_only). Also seen with gpt-5.6-terra.
What issue are you seeing?
In code mode, the size of a tool result the model receives is bounded only by what the model itself asked for:
codex-rs/core/src/tools/code_mode/mod.rs — truncate_code_mode_result uses resolve_max_tokens(max_output_tokens), i.e. the // @exec: {"max_output_tokens": N} pragma value (or 10 000 when absent), with no clamp;
codex-rs/core/src/tools/context.rs — the function-mode path ExecCommandToolOutput::model_output_max_tokens does clamp: resolve_max_tokens(self.max_output_tokens).min(self.truncation_policy.token_budget());
codex-rs/code-mode/src/description.rs only checks the pragma is a non-negative safe integer.
So a script that raises the pragma passes a raw shell result of any size (up to the 1 MiB UNIFIED_EXEC_OUTPUT_MAX_BYTES buffer) straight into the context. Observed in an automated evaluation run (2026-09-02): an rg … | head -240 over a repository returned 496 887 bytes in 57 very long lines; the next model request carried 159 055 uncached input tokens (≈138K of them that tool result) and took 1 113 s to be answered against the Azure deployment (the same request shape on 2026-08-31, without a raised budget, was cut to ~10K tokens and answered in 2 s). Across ~680 shell results ≥100 KB in the same evaluation archive (gpt-5.6 models), exactly two reached the model uncut — so this is rare, but when it happens it costs a turn's worth of time and tokens and the harness has no knob: tool_output_token_limit only rewrites model.truncation_policy, which the code-mode result path never reads; the catalog's tool_mode overrides features.code_mode* (session/turn_context.rs), so the harness cannot leave code mode either.
Related but the inverse framing: #33402 (outer exec truncates below nested budgets). This report is about the upper direction.
Expected
Code-mode exec/wait results are clamped the way function-mode results already are — min(requested, truncation_policy.token_budget()) (or a configurable ceiling) — so a model cannot request more tool output than the model's own output policy allows.
Steps to reproduce
Any code-mode model (catalog tool_mode: code_mode_only, e.g. gpt-5.6-*).
Have the model run an exec script whose first line is // @exec: {"max_output_tokens": 200000} and whose body calls exec_command({ cmd: "yes 'x…(8 KB line)' | head -60" }) and text(result.output).
In the rollout JSONL, the custom_tool_call_output carries the full ~480 KB; the next request's input_tokens grows by ~130K.
I rechecked this against current main at 728cb12fe5794b0c3a8e776fb4994b1650b973a8; the code-mode path is still not clamped by the model truncation policy.
I have now published the one-file reference implementation from the analysis above:
passes the current model TruncationPolicy into all 3 code-mode result paths;
uses the same stricter-by-byte_budget() precedence already used by function-mode ExecCommandToolOutput::model_output_policy();
preserves a smaller explicit max_output_tokens;
adds a focused regression where requested Tokens(100) is clamped by model Tokens(5);
keeps the existing text/audio truncation implementation unchanged.
Local source/diff validation on the exact upstream base:
base: 728cb12fe5794b0c3a8e776fb4994b1650b973a8
changed files: 1
production call sites migrated: 3/3
old two-argument production calls remaining: 0
git diff --check: PASS
working tree after commit/push: clean
Validation boundary: this Windows host does not currently have cargo / rustc, so I am not claiming Rust compile or unit-test success.
I also checked docs/contributing.md before attempting an upstream PR. The repository explicitly says external code contributions / pull requests are not accepted and asks community contributors to share root-cause analysis and potential fix approaches through issues instead. I therefore will not open an upstream PR; the fork commit is only a concrete reference implementation for maintainer review if useful.
tmatup's root-cause is correct — I implemented and validated the clamp on current main (7eee24ef51).
The code-mode result path (codex-rs/core/src/tools/code_mode/mod.rstruncate_code_mode_result) used resolve_max_tokens(max_output_tokens) with no clamp, so a script's raised @exec max_output_tokens pragma could push a large shell result into context. The function-mode path already clamps via ExecCommandToolOutput::model_output_policy (min(requested, truncation_policy)).
The fix threads the model's truncation_policy into truncate_code_mode_result and clamps the requested budget to it — mirroring the function-mode clamp exactly, so a model never receives a tool result larger than its own output policy allows.
Note: upstream PR creation is blocked for contributors (GraphQL CreatePullRequest), so I've put the validated change on a fork: argszero/codex branch fix/code-mode-clamp-output → PR #2. Happy to hand it off to anyone with write access.
Validated against current main:
cargo clippy -p codex-core --lib: clean
codex-corecode_mode unit tests: 36/36 pass (existing + new high_max_output_tokens_is_clamped_to_model_policy, which raises the pragma to 10,000 tokens and asserts the result is still clamped to a 25-token model policy)
Activity
fscfede-beep commentedon Sep 3, 2026
I rechecked this against current
mainat728cb12fe5794b0c3a8e776fb4994b1650b973a8; the code-mode path is still not clamped by the model truncation policy.I have now published the one-file reference implementation from the analysis above:
fix/code-mode-clamp-output-policyThe patch:
TruncationPolicyinto all 3 code-mode result paths;byte_budget()precedence already used by function-modeExecCommandToolOutput::model_output_policy();max_output_tokens;Tokens(100)is clamped by modelTokens(5);Local source/diff validation on the exact upstream base:
728cb12fe5794b0c3a8e776fb4994b1650b973a8git diff --check: PASSValidation boundary: this Windows host does not currently have
cargo/rustc, so I am not claiming Rust compile or unit-test success.I also checked
docs/contributing.mdbefore attempting an upstream PR. The repository explicitly says external code contributions / pull requests are not accepted and asks community contributors to share root-cause analysis and potential fix approaches through issues instead. I therefore will not open an upstream PR; the fork commit is only a concrete reference implementation for maintainer review if useful.argszero commentedon Sep 3, 2026
tmatup's root-cause is correct — I implemented and validated the clamp on current main (
7eee24ef51).The code-mode result path (
codex-rs/core/src/tools/code_mode/mod.rstruncate_code_mode_result) usedresolve_max_tokens(max_output_tokens)with no clamp, so a script's raised@exec max_output_tokenspragma could push a large shell result into context. The function-mode path already clamps viaExecCommandToolOutput::model_output_policy(min(requested, truncation_policy)).The fix threads the model's
truncation_policyintotruncate_code_mode_resultand clamps the requested budget to it — mirroring the function-mode clamp exactly, so a model never receives a tool result larger than its own output policy allows.Note: upstream PR creation is blocked for contributors (GraphQL
CreatePullRequest), so I've put the validated change on a fork:argszero/codexbranchfix/code-mode-clamp-output→ PR #2. Happy to hand it off to anyone with write access.Validated against current main:
cargo clippy -p codex-core --lib: cleancodex-corecode_modeunit tests: 36/36 pass (existing + newhigh_max_output_tokens_is_clamped_to_model_policy, which raises the pragma to 10,000 tokens and asserts the result is still clamped to a 25-token model policy)