Codex remote compaction deadlocks when input_image payloads remain in compacted replacement history
What version of Codex CLI is running?
codex-cli 0.133.0
What subscription do you have?
Pro Lite
Which model were you using?
gpt-5.5 with reasoning_effort=xhigh
What platform is your computer?
Linux 6.12.85+deb13-amd64 x86_64 unknown
What terminal emulator and version are you using (if applicable)?
GNOME Terminal, TERM=xterm-256color, COLORTERM=truecolor. No tmux/screen/zellij for the checked command environment. The affected sessions were long-lived local Codex app-server sessions launched through a wrapper, not ordinary one-off terminal chats.
Codex doctor report
{
"schemaVersion": 1,
"generatedAt": "1779667950s since unix epoch",
"overallStatus": "fail",
"codexVersion": "0.133.0",
"checks": {
"app_server.status": {
"id": "app_server.status",
"category": "app-server",
"status": "ok",
"summary": "background server is not running",
"details": {
"control socket": "/home/mrenda/.codex/app-server-control/app-server-control.sock",
"daemon state dir": "/home/mrenda/.codex/app-server-daemon",
"mode": "ephemeral",
"pid file": "/home/mrenda/.codex/app-server-daemon/app-server.pid (missing)",
"settings": "/home/mrenda/.codex/app-server-daemon/settings.json (missing)",
"status": "not running",
"update-loop pid file": "/home/mrenda/.codex/app-server-daemon/app-server-updater.pid (missing)"
},
"remediation": null,
"durationMs": 0
},
"auth.credentials": {
"id": "auth.credentials",
"category": "auth",
"status": "ok",
"summary": "auth is configured",
"details": {
"auth env vars present": "OPENAI_API_KEY",
"auth file": "/home/mrenda/.codex/auth.json",
"auth storage mode": "File",
"stored API key": "false",
"stored ChatGPT tokens": "true",
"stored agent identity": "false",
"stored auth mode": "chatgpt"
},
"remediation": null,
"durationMs": 0
},
"config.load": {
"id": "config.load",
"category": "config",
"status": "ok",
"summary": "config loaded",
"details": {
"CODEX_HOME": "/home/mrenda/.codex",
"config.toml": "/home/mrenda/.codex/config.toml",
"config.toml parse": "ok",
"cwd": "/home/mrenda/projects/kuzys",
"model": "gpt-5.5",
"model provider": "openai",
"sqlite home": "/home/mrenda/.codex"
},
"remediation": null,
"durationMs": 0
},
"installation": {
"id": "installation",
"category": "install",
"status": "ok",
"summary": "installation looks consistent",
"details": {
"PATH codex #1": "/usr/local/bin/codex",
"managed by npm": "true",
"managed package root": "/usr/local/lib/node_modules/@openai/codex",
"npm update target": "/usr/local/lib/node_modules/@openai/codex"
},
"remediation": null,
"durationMs": 392
},
"runtime.provenance": {
"id": "runtime.provenance",
"category": "runtime",
"status": "ok",
"summary": "running npm on linux-x86_64",
"details": {
"platform": "linux-x86_64",
"version": "0.133.0"
},
"remediation": null,
"durationMs": 0
},
"state.paths": {
"id": "state.paths",
"category": "state",
"status": "ok",
"summary": "state paths and databases are inspectable",
"details": {
"CODEX_HOME": "/home/mrenda/.codex (dir)",
"active rollout files": "191 files, 5226839601 total bytes, 27365652 average bytes",
"state DB": "/home/mrenda/.codex/state_5.sqlite (file)",
"state DB integrity": "ok"
},
"remediation": null,
"durationMs": 385
},
"terminal.env": {
"id": "terminal.env",
"category": "terminal",
"status": "ok",
"summary": "terminal metadata was detected",
"details": {
"COLORTERM": "truecolor",
"terminal": "GNOME Terminal",
"terminal size": "80x24"
},
"remediation": null,
"durationMs": 2
}
}
}
What issue are you seeing?
Long-lived Codex sessions can become unable to resume after remote pre-turn compaction fails with context_length_exceeded.
The affected sessions had input_image payloads preserved inside the latest rollout type=compacted record, under payload.replacement_history.
When Codex later attempted remote pre-turn compaction, those image payloads were included in the compaction request. The request exceeded the model context window, and the session could not
continue.
User-facing error:
```text
Codex ran out of room in the model's context window. Start a new thread or clear earlier history before retrying.
Error running remote compact task:
{
"error": {
"message": "Your input exceeds the context window of this model. Please adjust your input and try again.",
"type": "invalid_request_error",
"param": "input",
"code": "context_length_exceeded"
}
}
Logs included:
codex_core::compact_remote: remote compaction failed
compact_error.code = context_length_exceeded
Failed to run pre-sampling compact
Example logged sizes:
failing_compaction_request_model_visible_bytes=19638335
failing_compaction_request_model_visible_bytes=13626592
failing_compaction_request_model_visible_bytes=9091729
failing_compaction_request_model_visible_bytes=2172694
This creates a deadlock: the session needs compaction to continue, but compaction cannot run because its own input is too large.
### What steps can reproduce the bug?
Uploaded thread: 019e0a2b-ffe3-7332-9276-6f6832f8680d
A minimal structural reproduction is:
1. Use a long-lived Codex session that includes image inputs/screenshots.
2. Let Codex perform remote compaction.
3. Inspect the latest rollout `type=compacted` record in `~/.codex/sessions/...`.
4. Confirm that `payload.replacement_history` contains a user message with `content` parts including `input_image`.
5. Continue the session until Codex tries pre-turn remote compaction again.
6. Observe remote compaction failing with `context_length_exceeded`.
Problematic data shape:
```json
{
"type": "compacted",
"payload": {
"replacement_history": [
{
"type": "message",
"role": "user",
"content": [
{ "type": "input_text", "text": "..." },
{ "type": "input_image", "image_url": "data:image/png;base64,..." },
{ "type": "input_text", "text": "..." }
]
}
]
}
}
Observed affected local threads contained:
- 2 image payloads / ~530 KB image JSON
- 4 image payloads / ~669 KB image JSON
- 9 image payloads / ~2.0 MB image JSON
- 21 image payloads / ~9.2 MB image JSON
After manually replacing only those input_image parts in the latest compacted replacement history with small text markers, the sessions were able to resume without creating new threads.
Uploaded thread 019e0a2b-ffe3-7332-9276-6f6832f8680d contains the investigation and mitigation discussion. The actual affected private rollout files cannot be uploaded because they contain private
session data.
### What is the expected behavior?
Remote compaction should not preserve raw historical `input_image` payloads indefinitely inside active compacted replacement history.
Expected behavior would be one of:
1. omit `input_image` parts from compacted replacement history;
2. replace historical image payloads with lightweight markers;
3. summarize image existence without keeping base64/data URL payloads;
4. strip image payloads before sending history to the remote compaction model;
5. provide a built-in recovery path if compaction fails due to oversized historical payloads.
A session should not become permanently unable to resume because the compaction request itself exceeds context.
### Additional information
Local mitigation used successfully:
1. Back up `~/.codex`.
2. Create a single-file backup of the affected rollout.
3. Modify only the latest `type=compacted` record.
4. Inside `payload.replacement_history`, replace only `input_image` parts with small textual markers.
5. Leave thread id, DB metadata, message order, summaries, text content, and session pointers unchanged.
6. Validate JSONL after modification.
After this surgical repair, affected sessions resumed successfully without starting new threads.
This strongly suggests that preserved `input_image` payloads inside compacted replacement history are the direct trigger.
Privacy note: I cannot provide full rollout files because they contain private session data. I can provide redacted JSON shapes, exact log lines, structural counts, before/after hashes, and
details of the successful mitigation.
7 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Fix: Remote Compaction Deadlock When input_image Payloads Remain in Compacted Replacements
Issue: openai/codex #24388
Component:
codex-rs/core/src/compact_remote_v2.rsSeverity: High — causes infinite compaction retry loops on conversations containing image inputs
---
1. Root Cause Analysis
1.1 Symptom
When a conversation thread contains user messages with
input_imagecontent items (base64-encoded image data URLs), remote compaction (v2) enters a deadlock cycle:InputImagepayloads1.2 Technical Root Cause
The deadlock originates in
codex-rs/core/src/compact_remote_v2.rsin the retained-message truncation logic:message_text_token_countreturns0forContentItem::InputImageitems. This is intentional for text token budgeting (images are billed differently by the API), but it creates a critical problem in the truncation loop:And critically,
truncate_message_text_to_token_budgetalways preservesInputImageitems:1.3 Deadlock Sequence
1.4 Why V1 Doesn't Have This Problem
The V1 remote compaction (
compact_remote.rs) uses the/responses/compactendpoint directly, which returns an encrypted compaction summary. The retained messages are filtered more aggressively:The V1 path relies on the server-side compact endpoint to produce a clean summary, and the client-side filtering is applied to the server's output, not the original input. The V2 path (
compact_remote_v2.rs) uses a streaming approach that retains original user messages fromprompt_input, which includes the raw image data.---
2. Step-by-Step Fix Approach
2.1 Overview
The fix strips
InputImagecontent items from retained messages during remote compaction v2, replacing them with a text placeholder. This ensures:2.2 Changes Required
File:
codex-rs/core/src/compact_remote_v2.rsThree functions need modification:
message_text_token_count— No change needed (correctly returns 0 for images; the fix is elsewhere)truncate_message_text_to_token_budget— ReplaceInputImagewith placeholder instead of preservingtruncate_retained_messages_for_remote_compaction— Add a pre-pass to strip images from all retained messagesAdditionally, add a new helper function to strip images from a single message.
2.3 Detailed Fix
Step 1: Add image-stripping helper
Add a new function to replace
InputImagecontent items with a text placeholder:Step 2: Modify
truncate_message_text_to_token_budgetReplace the line that preserves
InputImageitems:Step 3: Apply image stripping in
build_v2_compacted_historyAdd image stripping to the retained message pipeline:
2.4 Alternative Approach (Pre-pass)
If modifying
build_v2_compacted_historyis undesirable, apply stripping as a pre-pass intruncate_retained_messages_for_remote_compaction:---
3. Code Snippets
3.1 Full Patch for
compact_remote_v2.rs3.2 New Tests
---
4. Testing Strategy
4.1 Unit Tests
| Test | Purpose |
|------|---------|
|
strip_image_content_from_message_replaces_images_with_placeholder| Verify images are replaced with placeholder text ||
strip_image_content_from_message_returns_none_for_image_only| Verify image-only messages become placeholder-only (not dropped) ||
strip_image_content_from_message_preserves_non_message_items| Verify non-Message ResponseItems pass through unchanged ||
build_v2_compacted_history_strips_images_from_retained_messages| Verify end-to-end image stripping in compacted history ||
build_v2_compacted_history_no_deadlock_with_many_images| Verify many image messages don't cause deadlock ||
retained_history_truncation_strips_images_before_budgeting| Verify images are stripped before token budget is applied |4.2 Integration Tests
| Test | Purpose |
|------|---------|
|
remote_compact_v2_with_image_input| Full compaction flow with image-containing user messages ||
remote_compact_v2_no_context_overflow_after_compaction| Verify post-compaction request fits within context window ||
remote_compact_v2_multiple_compactions_with_images| Verify multiple sequential compactions don't deadlock ||
remote_compact_v2_image_placeholder_preserves_semantics| Verify model can understand the placeholder text |4.3 Regression Tests
The existing tests in
compact_remote_v2.rsneed updates:retained_history_truncation_preserves_images_and_truncates_later_text_parts— This test currently expects images to be preserved. It must be updated to expect the placeholder instead.retained_history_truncation_charges_image_only_messages— Update to verify image-only messages become placeholder-only messages.retained_history_truncation_drops_image_only_messages_after_budget_is_spent— This test's behavior changes: image-only messages will now become placeholder-only messages, which have a non-zero token cost.4.4 Test Execution
---
5. Impact Assessment
5.1 Breaking Changes
None for end users. The fix is internal to the compaction pipeline. The placeholder text
[Image omitted during compaction]is visible to the model but is designed to be semantically clear.5.2 Behavioral Changes
| Aspect | Before | After |
|--------|--------|-------|
| Image payloads in compacted history | Retained as-is (base64 data URLs) | Replaced with
[Image omitted during compaction]|| Token cost of image-containing messages | Underestimated (images counted as 0 tokens) | Accurate (placeholder text has real token cost) |
| Compaction deadlock with images | Possible | Eliminated |
| Model awareness of images | Full image data available | Placeholder indicates image was present |
5.3 Performance Impact
5.4 Risk Assessment
| Risk | Likelihood | Mitigation |
|------|-----------|------------|
| Model confusion from placeholder | Low | Placeholder text is explicit and clear |
| Loss of image context for follow-up turns | Medium | Compaction summary should capture image semantics; the placeholder signals an image was present |
| Existing tests failing | High | Tests must be updated to expect new behavior |
| Rollout compatibility | Low | Change is backward-compatible; old rollouts will simply have images stripped on next compaction |
5.5 Migration Path
No migration needed. The fix is applied at compaction time:
5.6 Monitoring Recommendations
After deployment, monitor:
---
6. Files Modified
| File | Change |
|------|--------|
|
codex-rs/core/src/compact_remote_v2.rs| Addstrip_image_content_from_message, modifybuild_v2_compacted_history, modifytruncate_message_text_to_token_budget||
codex-rs/core/src/compact_remote_v2.rs(tests) | Update existing tests, add new tests for image stripping |---
7. Appendix: Related Code Paths
7.1 V1 Remote Compaction (
compact_remote.rs)The V1 path uses the
/responses/compactendpoint and does not retain original user messages with image payloads in the same way. Theshould_keep_compacted_history_itemfilter operates on the server's output, not the client's input. No changes needed for V1.7.2 Local Compaction (
compact.rs)Local compaction uses
collect_user_messageswhich extracts text viaUserMessageItem::message(). This method already ignores image content (returns empty string for non-text items). No changes needed for local compaction.7.3 Context Manager Normalization (
context_manager/normalize.rs)The
strip_images_when_unsupportedfunction already handles stripping images when the model doesn't support them. This is orthogonal to the compaction fix — it applies at request time, not compaction time. No changes needed.7.4 Token Estimation (
context_manager/history.rs)The
estimate_response_item_model_visible_bytesfunction already applies a heuristic for image token costs (RESIZED_IMAGE_BYTES_ESTIMATE = 7373 bytes ≈ 1844 tokens). This is used for context window management but not for compaction retention decisions. No changes needed, but confirms that images have significant token cost.I can confirm this failure mode on Windows / Codex Desktop as well.
In my case, a long-running Codex Desktop thread failed remote compaction with the same class of error:
stream disconnected before completion: unknown variant auto, expected high or originalThe relevant local evidence was in the latest
type:"compacted"JSONL record. Itspayload.replacement_historystill contained historicalinput_imageentries with largedata:image...payloads.A narrow local mitigation worked:
contentitems withtype:"input_image"insidepayload.replacement_historyinput_textplaceholder insteadAfter this, the thread could compact again. This was not solved by treating it as a generic context-window overflow; the image payloads in replacement history were the decisive factor.
I’m seeing what appears to be the same failure mode: a Codex thread that involved reading image inputs eventually hit a hard model context limit during continuation/compaction.
Environment:
Observed error:
<img width="854" height="151" alt="Image" src="https://github.com/user-attachments/assets/8244d884-0231-4574-9b10-3da71ef5939a" />
Thanks for reporting this problem. Until recently, Codex used a separate endpoint and server-side logic for compaction. We recently switched to a more robust approach where the compaction logic is moved locally using the same endpoint as normal turns. This eliminates "remote compaction" errors and increases the stability and reliability of compaction operations. If you see further problems with compaction, please use
/feedbackand open a new bug report.Codex Desktop on Windows still stalls for about 3–4 seconds after switching to a large existing thread. The thread becomes visible first, but the input box remains unresponsive during that period.
Environment:
Findings:
excludeTurns: true
initialTurnsPage: { limit: 16, sortDirection: "desc", itemsView: "full" }
reduced the initial response to about 52 KB and improved responsiveness.
Suggested improvements:
I can provide sanitized statistics, but cannot upload raw rollout files because they contain private conversation history.
A clarification for future readers: I believe “the compaction logic is moved locally using the same endpoint as normal turns” refers to remote compaction v2, introduced in #20773 and enabled by default for eligible providers in #27573.
This does not mean Codex returned to the older prompt-based local compaction path. In v2, Codex assembles the compaction request and rebuilds replacement history client-side, but sends a
compaction_triggerthrough the normal/responsestransport. The backend returns an opaquecompactionitem containingencrypted_content, which Codex carries into later turns. The dedicated/responses/compactrequest and server-rebuilt history are the parts that were replaced; the source still calls the new pathremote_compaction_v2.So “local” here describes orchestration and history reconstruction, not a plaintext local summarization turn. It also does not by itself establish that the original
input_imageretention issue was fixed: #23728 explicitly preserved non-text retained content such as images when rebuilding v2 history, and the current implementation still retains and counts those images.