Bug: `trim_function_call_history_to_fit_context_window` breaks early on non-rewriteable items, leaving context overflows untrimmed
What version of Codex CLI is running?
Reproduced with codex-cli 0.145.0. The same implementation is still present in main at e4fb5311d7468839def62eabda4b268f4a54cf11.
What platform is your computer?
Darwin 26.5.2 arm64 arm (macOS).
What issue are you seeing?
trim_function_call_history_to_fit_context_window breaks its truncation loop as soon as it encounters a non-rewriteable item, even if earlier (older) tool outputs could still be trimmed. This leaves the context window over its limit when non-rewriteable items (assistant messages, reasoning, etc.) sit between trimmable tool outputs.
Root cause: The function walks history from newest to oldest and breaks at the first non-rewriteable item:
// compact_remote.rs:390-401
for (item, item_tokens) in original_items.iter().zip(item_token_estimates).rev() {
if i64::try_from(estimated_tokens).unwrap_or(i64::MAX) <= context_window {
break;
}
let Some(rewritten_item) = rewritten_output_for_context_window(item) else {
break; // <-- stops here even if earlier outputs could be trimmed
};
...
}
The rewritten_output_for_context_window function (line 416) only rewrites FunctionCallOutput, CustomToolCallOutput, and ToolSearchOutput. Any other item type (assistant messages, reasoning, user messages) causes the loop to break.
Example scenario:
History structure (newest first):
[FunctionCallOutput_3, AssistantMessage_2, FunctionCallOutput_1, UserMessage]
If the context window is exceeded and only FunctionCallOutput_3 is trimmed, the overflow persists because AssistantMessage_2 blocks trimming of FunctionCallOutput_1.
Impact: The context window remains over its limit after trimming, which can cause:
- The compaction request to fail with
ContextWindowExceeded - Subsequent turns to trigger aggressive compaction that removes history items
- User-visible "context window exceeded" errors
What steps can reproduce the bug?
- Build a conversation with alternating assistant messages and tool outputs
- Continue until the context window is near capacity
- Trigger a compaction or context trim
- The trim will only affect trailing tool outputs, leaving earlier overflows unaddressed
What is the expected behavior?
The function should skip non-rewriteable items and continue trimming earlier tool outputs until the context window is within limits or no more trimmable items remain.
Suggested fix
Change the break to continue for non-rewriteable items, but track whether we've already trimmed something to avoid infinite loops:
for (item, item_tokens) in original_items.iter().zip(item_token_estimates).rev() {
if i64::try_from(estimated_tokens).unwrap_or(i64::MAX) <= context_window {
break;
}
let Some(rewritten_item) = rewritten_output_for_context_window(item) else {
continue; // Skip non-rewriteable items, keep looking
};
estimated_tokens = estimated_tokens
.saturating_sub(i128::from(item_tokens))
.saturating_add(i128::from(estimate_item_token_count(&rewritten_item)));
rewritten_items.push(rewritten_item);
}
Note: This requires updating the rewritten_items logic since items are now collected out of order. The final assembly would need to replace items at their original positions rather than appending.
Related
- #32556 — Context window usage appears to grow unexpectedly during long sessions
- #23643 — Window/Context fills up enormously fast
Scope
One function in one file (compact_remote.rs:365-414). The fix is ~15 lines. No behavioral change for正常 trimming — only affects the case where non-rewriteable items block trimming.