Auto-spill large tool outputs to files instead of silently truncating

Open 💬 3 comments Opened Mar 10, 2026 by banteg

What version of Codex CLI is running?

codex-cli 0.113.0

What subscription do you have?

2x Pro

Which model were you using?

gpt-5.4

What platform is your computer?

Darwin 24.6.0 arm64 arm

What terminal emulator and version are you using (if applicable)?

Ghostty 1.3.0-main+f1e6c6f5a

What issue are you seeing?

When a tool result exceeds tool_output_token_limit, Codex currently truncates the payload in place before it is kept in model-visible history. That is a bad default for tool outputs.

The bigger issue is that Codex silently changes the semantics of the tool result:

  • structured payloads can become invalid or unparsable,
  • logs, traces, and tabular output lose the middle, which is often where the answer actually is,
  • MCP responses stop being exact tool results and become lossy previews,
  • there is no recovery path inside the same turn because the omitted bytes are not spilled anywhere the model can refer back to.

The current behavior is acceptable for a UI preview, but it is not a good contract for machine-consumed tool output. A tool call should either return the real result, or return a structured reference to the real result. Mutating the result into a head/tail snippet with an inline …N tokens truncated… marker is the worst of both worlds: it preserves neither exactness nor recoverability.

This is especially problematic for MCP tools and shell commands that return large JSON, XML, stack traces, test failures, (de)compiler output, search results, or audit logs. In all of those cases, truncation can remove the only relevant lines while still leaving the model with something that looks authoritative enough to act on.

What steps can reproduce the bug?

  1. Set a small tool output budget in ~/.codex/config.toml:
tool_output_token_limit = 500
  1. Start Codex with gpt-5.4.
  1. Ask Codex to run a command that emits a large structured payload, for example:
uv run python - <<'PY'
import json

payload = {
    "header": "begin",
    "items": [{"idx": i, "payload": "x" * 200} for i in range(300)],
    "footer": "end",
}

print(json.dumps(payload))
PY
  1. Then ask Codex a question that depends on content in the middle of the output, for example:
How many items are there, and what is the payload length for item 150?
  1. Observe that the tool result is truncated in place with an inline marker such as …N tokens truncated…, and there is no artifact ID, temp file path, or follow-up handle for the omitted data.
  1. If the output is structured (JSON/XML/etc.), it may also no longer be valid for downstream parsing.

The same problem shows up with MCP tools that return large JSON responses, search results, logs, or other exact payloads that the model may need to inspect incrementally.

What is the expected behavior?

If tool output exceeds the configured budget, Codex should auto-spill the full payload to a local temporary artifact and give the model a structured reference plus a preview, rather than silently truncating the only copy it sees.

Concretely, something like this would be much safer:

  • keep the full tool output in a temp file or artifact under Codex-managed state,
  • send the model a compact envelope containing:
  • artifact ID and/or file path,
  • content type if known,
  • original byte/token/line size,
  • a short preview or head/tail excerpt,
  • an explicit notice that the inline content is only a preview,
  • allow follow-up reads against that artifact (full read, ranged read, grep/search, or structured re-read),
  • keep inline truncation only as a fallback when spilling is disabled or impossible.

The key property is recoverability: once a tool finishes successfully, the full result should still exist somewhere locally, and the model should be able to ask for the omitted parts on demand. That is a much better default than lossy in-place truncation.

Additional information

Related issue: #9504 discusses structured JSON/XML truncation, but this issue is broader. The core problem is not just parseability. It is that oversized tool outputs are currently converted into lossy previews with no artifact handle, regardless of whether the original payload was structured or plain text.

I checked the current implementation locally before writing this up. The truncation is intentional and happens in the tool output history path (core/src/context_manager/history.rs, truncate_function_output_payload(...)), and the current MCP truncation behavior is covered by core/tests/suite/truncation.rs.

This also seems aligned with the mitigation direction mentioned by maintainers in #9504: local temporary files are a better general solution than heuristic format-specific truncation.

The proposed auto-spill solution in inspired by banteg/bn tool auto-spill policy.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗