Rollouts persist each command's stdout 3x per item_completed record (stdout/aggregated_output/formatted_output) — 60% of session bytes; thread_history stores a 4th copy
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What version of Codex CLI is running?
codex-cli 0.150.1 (also Codex Desktop app-server 151.0.7922.170 on the same ~/.codex)
What subscription do you have?
Pro
Which model were you using?
gpt-5.6-sol
What platform is your computer?
Darwin 25.5.0 arm64 arm
What terminal emulator and version are you using (if applicable)?
Orca (terminal multiplexer/agent launcher) driving codex TUI sessions; Codex Desktop app-server also running against the same CODEX_HOME.
Codex doctor report
See "Additional information" (full JSON attached at the bottom; nothing redacted was needed).
What issue are you seeing?
~/.codex reached 14 GB on a machine where Codex is the secondary agent (Claude Code is primary). After removing rollouts older than 30 days, the last 30 days alone are 6.7 GB across 5,271 rollout files. Sizing what is inside them shows the dominant cost is not model traffic but the same command stdout being persisted four times — three copies inside a single rollout record, plus one more in thread_history_1.sqlite.
1. event_msg/item_completed for CommandExecution stores the full stdout three times in one record.
Each completed command is appended to the rollout as an event_msg whose item has stdout, stderr, aggregated_output, and formatted_output. Measured on the largest rollout (114 MB, 1,359 CommandExecution items):
| field | bytes | note |
|---|---|---|
stdout |
27.9 MB | |
aggregated_output |
27.9 MB | byte-identical to stdout in 1,359 / 1,359 items |
formatted_output |
16.8 MB | byte-identical to aggregated_output in 1,186 / 1,359 items |
stderr |
~0 |
That one record type is 75 MB of the 114 MB file (68%). The response_item/function_call_output / custom_tool_call_output records — the (truncated) output the model actually saw — total only 22 MB in the same file. So the rollout carries ~3.4× more untruncated stdout than model-visible output.
Across the 200 largest rollouts in ~/.codex/sessions (2.0 GB total):
| record type | bytes | share |
|---|---|---|
event_msg/item_completed (CommandExecution) |
1,199 MB | 59.9% |
response_item/custom_tool_call_output |
311 MB | 15.5% |
response_item/function_call_output |
128 MB | 6.4% |
response_item/reasoning |
110 MB | 5.5% |
| everything else | ~250 MB | ~12% |
Inside those CommandExecution items: stdout 461 MB + aggregated_output 461 MB + formatted_output 236 MB.
2. thread_history_1.sqlite stores a fourth copy.
The projection DB is 1.68 GB; thread_items rows with item_type = 'commandExecution' account for 1,486 MB of it, and json_extract(item_json, '$.aggregatedOutput') alone sums to 1,416 MB (individual items cap at ~1 MB). Every thread in it still has its rollout on disk, so this is a straight duplicate of data already in sessions/.
3. There is no age-based retention for any of this, so it only grows. (Tracked separately in #6015 / #28187 / #20230 — noted here only because it turns the duplication into a permanent cost.) logs_2.sqlite separately sat at 2.63 GB with 1.76 GB of freelist pages; that is #35823 and VACUUM fixed it.
Why this is distinct from existing reports. #36557 / #39469 / #31198 / #29531 are about parent history or compacted.replacement_history being replayed into child/resumed rollouts; #24948 and #34061 attribute growth to raw function_call_output volume and event counts. In my data the response_item outputs are the small part. The cost is the three redundant fields on item_completed events plus the projection copy — it applies to every command in every session, subagent or not, and stacks multiplicatively on top of the issues above. Workload context: 77% of my rollouts (4,631 of 5,271; 5.1 GB) are subagent threads from a multi-agent plugin whose workers run many read-heavy shell commands (cat, grep, git show), so each byte of stdout costs ~4 bytes on disk, forever.
What steps can reproduce the bug?
- Run any session that executes a shell command with non-trivial stdout, e.g.
codex exec "run: cat some-200KB-file"(or any TUI session doing normal work). - Open the rollout under
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonland find theevent_msgrecord withpayload.type == "item_completed"andpayload.item.type == "CommandExecution". - Observe
item.stdout == item.aggregated_output == item.formatted_output(the last differs only when the formatted view truncates). - Observe the same output again, truncated, in the preceding
response_item(custom_tool_call_output/function_call_output). - Observe it a fourth time:
sqlite3 ~/.codex/thread_history_1.sqlite "select length(json_extract(item_json,'$.aggregatedOutput')) from thread_items where item_type='commandExecution' order by 1 desc limit 5".
Measurement script used for the numbers above (Python 3, stdlib only):
import json, sys, collections
by = collections.Counter(); fld = collections.Counter(); tot = 0
for path in sys.argv[1:]:
for line in open(path):
tot += len(line)
r = json.loads(line); p = r.get("payload", {})
t = r.get("type", "?") + "/" + str(p.get("type", ""))
if t == "event_msg/item_completed":
t += "/" + str(p.get("item", {}).get("type"))
by[t] += len(line)
if t == "event_msg/item_completed/CommandExecution":
it = p["item"]
for k in ("stdout", "stderr", "aggregated_output", "formatted_output"):
fld[k] += len(it.get(k) or "")
print(f"total {tot/2**20:.0f} MB")
for t, b in by.most_common(8): print(f"{b/2**20:7.0f} MB {100*b/tot:5.1f}% {t}")
for k, v in fld.items(): print(f"{v/2**20:7.0f} MB {k}")
Example thread id with the 114 MB rollout: 01a020c7-c50c-7850-b187-da14ba3adc88.
What is the expected behavior?
- A command's output is persisted once per rollout.
item_completedshould either omit the output fields (theresponse_itemalready carries what the model saw, and the raw output could be referenced by offset) or carry a single field;stdout/aggregated_output/formatted_outputshould not be three literal copies. thread_history_1.sqliteshould reference rollout offsets (it already storesrollout_byte_offsetper turn) rather than re-embeddingaggregatedOutput.- Some size cap or retention on persisted raw output, so a
catof a large file does not become a permanent multi-megabyte on-disk cost.
Additional information
Disk state when this was measured (macOS, single user, Codex used a few hours a day):
~/.codex/sessions: 6.7 GB for 30 days (5,271 rollouts; 4,631 subagent, 626 interactive). Before pruning >30-day files it was 8.5 GB.~/.codex/thread_history_1.sqlite: 1.68 GB (2,943 threads, all from the current month; 1,486 MB iscommandExecutionitems).~/.codex/logs_2.sqlite: 2.63 GB → 717 MB afterVACUUM(#35823).- Related: #24948, #36557, #31198, #39469, #34061 (growth from other duplication paths); #35823 (logs freelist); #6015, #28187, #20230 (retention/cleanup).
codex doctor --json
{
"schemaVersion": 1,
"generatedAt": "1787893861s since unix epoch",
"overallStatus": "warning",
"codexVersion": "0.150.1",
"checks": {
"app_server.status": {
"id": "app_server.status",
"category": "app-server",
"status": "ok",
"summary": "background server is not running",
"details": {
"control socket": "/Users/shaswat/.codex/app-server-control/app-server-control.sock",
"daemon state dir": "/Users/shaswat/.codex/app-server-daemon",
"mode": "ephemeral",
"pid file": "/Users/shaswat/.codex/app-server-daemon/app-server.pid (missing)",
"settings": "/Users/shaswat/.codex/app-server-daemon/settings.json (missing)",
"status": "not running",
"update-loop pid file": "/Users/shaswat/.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": "/Users/shaswat/.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": "/Users/shaswat/.codex",
"config.toml": "/Users/shaswat/.codex/config.toml",
"config.toml parse": "ok",
"cwd": "/Users/shaswat/.codex",
"enabled feature flags": "shell_tool, view_image, unified_exec, unified_exec_zsh_fork, shell_snapshot, code_mode_host, terminal_resize_reflow, sqlite, hooks, enable_request_compression, unbounded_connection_retries, multi_agent, multi_agent_v2, tool_search_always_defer_mcp_tools, tool_suggest, plugins, in_app_browser, in_app_chat, in_app_dictation, in_app_local_automation, in_app_updates, browser_use, browser_use_full_cdp_access, browser_use_external, computer_use, remote_plugin, plugin_sharing, image_generation, resize_all_images, item_ids, skill_mcp_dependency_install, skill_search, mentions_v2, steer, guardian_approval, goals, collaboration_modes, tool_call_mcp_elicitation, auth_elicitation, personality, fast_mode, tui_app_server, remote_compaction_v2, compaction_image_budget, workspace_dependencies",
"feature flag overrides": "multi_agent_v2=true, apps=false",
"feature flags enabled": "45",
"log dir": "/Users/shaswat/.codex/log",
"mcp servers": "3",
"model": "gpt-5.6-sol",
"model provider": "openai",
"sqlite home": "/Users/shaswat/.codex"
},
"remediation": null,
"durationMs": 0
},
"desktop.app.version": {
"id": "desktop.app.version",
"category": "desktop",
"status": "ok",
"summary": "the desktop application is installed",
"details": {
"log directory": "$HOME/Library/Logs/com.openai.codex",
"running": "true",
"version": "26.818.61809"
},
"remediation": null,
"durationMs": 0
},
"desktop.app_server.handshake": {
"id": "desktop.app_server.handshake",
"category": "desktop",
"status": "ok",
"summary": "no desktop app-server handshake was recorded",
"details": {},
"remediation": null,
"durationMs": 0
},
"desktop.security.enforcement": {
"id": "desktop.security.enforcement",
"category": "desktop",
"status": "ok",
"summary": "the desktop application passed available macos security assessments",
"details": {
"gatekeeper": "accepted"
},
"remediation": null,
"durationMs": 0
},
"git.environment": {
"id": "git.environment",
"category": "git",
"status": "ok",
"summary": "git version 2.50.1 (Apple Git-155)",
"details": {
"PATH git #1": "/usr/bin/git",
"PATH git entries": "1",
"git build options": "git version 2.50.1 (Apple Git-155); cpu: arm64; no commit associated with this build; sizeof-long: 8; sizeof-size_t: 8; shell-path: /bin/sh; feature: fsmonitor--daemon; libcurl: 8.7.1; zlib: 1.2.12; SHA-1: SHA1_DC; SHA-256: SHA256_BLK",
"git exec path": "/Library/Developer/CommandLineTools/usr/libexec/git-core",
"git version": "git version 2.50.1 (Apple Git-155)",
"repo detected": "false",
"selected git": "/usr/bin/git"
},
"remediation": null,
"durationMs": 146
},
"installation": {
"id": "installation",
"category": "install",
"status": "ok",
"summary": "installation looks consistent",
"details": {
"PATH codex #1": "/opt/homebrew/bin/codex",
"current executable": "/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/bin/codex",
"install context": "npm (package /opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin, bin /opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/bin, resources /opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-resources, path /opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-path)",
"managed by bun": "false",
"managed by npm": "true",
"managed by pnpm": "false",
"managed package root": "/opt/homebrew/lib/node_modules/@openai/codex",
"npm update target": "/opt/homebrew/lib/node_modules/@openai/codex"
},
"remediation": null,
"durationMs": 191
},
"mcp.config": {
"id": "mcp.config",
"category": "mcp",
"status": "ok",
"summary": "MCP configuration is locally consistent",
"details": {
"configured servers": "3",
"disabled servers": "1",
"stdio servers": "2",
"streamable_http servers": "1"
},
"remediation": null,
"durationMs": 250
},
"network.env": {
"id": "network.env",
"category": "network",
"status": "ok",
"summary": "network-related environment looks readable",
"details": {
"managed proxy": "not configured",
"proxy env vars": "none",
"respect system proxy": "disabled",
"system proxy": "direct"
},
"remediation": null,
"durationMs": 1
},
"network.provider_reachability": {
"id": "network.provider_reachability",
"category": "reachability",
"status": "ok",
"summary": "active provider endpoints are reachable over HTTP",
"details": {
"ChatGPT inference URL": "https://chatgpt.com/backend-api/<redacted> reachable (HTTP 405)",
"desktop assets CDN": "https://chatgpt.com/backend-api/<redacted> reachable (HTTP 200)",
"reachability mode": "ChatGPT auth"
},
"remediation": null,
"durationMs": 274
},
"network.websocket_reachability": {
"id": "network.websocket_reachability",
"category": "websocket",
"status": "ok",
"summary": "Responses WebSocket handshake succeeded",
"details": {
"DNS": "2 IPv4, 2 IPv6, first IPv6",
"auth mode": "chatgpt",
"connect timeout": "15000 ms",
"endpoint": "wss://chatgpt.com/backend-api/<redacted>",
"handshake result": "HTTP 101 Switching Protocols",
"model provider": "openai",
"provider name": "OpenAI",
"proxy env vars": "none",
"reasoning header": "false",
"server model present": "false",
"supports websockets": "true",
"wire API": "responses"
},
"remediation": null,
"durationMs": 1130
},
"runtime.provenance": {
"id": "runtime.provenance",
"category": "runtime",
"status": "ok",
"summary": "running npm on macos-aarch64",
"details": {
"commit": "unknown",
"current executable": "/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/bin/codex",
"install method": "npm (package /opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin, bin /opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/bin, resources /opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-resources, path /opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-path)",
"platform": "macos-aarch64",
"version": "0.150.1"
},
"remediation": null,
"durationMs": 0
},
"runtime.search": {
"id": "runtime.search",
"category": "search",
"status": "ok",
"summary": "search is OK (bundled)",
"details": {
"search command": "/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-path/rg",
"search command readiness": "file exists",
"search provider": "bundled"
},
"remediation": null,
"durationMs": 0
},
"sandbox.helpers": {
"id": "sandbox.helpers",
"category": "sandbox",
"status": "ok",
"summary": "sandbox configuration is readable",
"details": {
"approval policy": "OnRequest",
"codex-linux-sandbox helper": "none",
"execve wrapper helper": "/Users/shaswat/.codex/tmp/arg0/codex-arg06QuFb4/codex-execve-wrapper",
"filesystem sandbox": "restricted",
"network sandbox": "enabled"
},
"remediation": null,
"durationMs": 0
},
"security.endpoint": {
"id": "security.endpoint",
"category": "security",
"status": "ok",
"summary": "no supported endpoint protection detected",
"details": {
"endpoint products": "none detected"
},
"remediation": null,
"durationMs": 92
},
"state.paths": {
"id": "state.paths",
"category": "state",
"status": "ok",
"summary": "state paths and databases are inspectable",
"details": {
"CODEX_HOME": "/Users/shaswat/.codex (dir)",
"active rollout files": "5271 files, 7021023063 total bytes, 1332009 average bytes",
"archived rollout files": "0 files, 0 total bytes, 0 average bytes",
"goals DB": "/Users/shaswat/.codex/goals_1.sqlite (file)",
"goals DB integrity": "ok",
"log DB": "/Users/shaswat/.codex/logs_2.sqlite (file)",
"log DB integrity": "ok",
"log dir": "/Users/shaswat/.codex/log (dir)",
"memories DB": "/Users/shaswat/.codex/memories_1.sqlite (file)",
"memories DB integrity": "ok",
"queue DB": "/Users/shaswat/.codex/queue_1.sqlite (file)",
"queue DB integrity": "ok",
"sqlite home": "/Users/shaswat/.codex (dir)",
"state DB": "/Users/shaswat/.codex/state_5.sqlite (file)",
"state DB integrity": "ok",
"thread history DB": "/Users/shaswat/.codex/thread_history_1.sqlite (file)",
"thread history DB integrity": "ok"
},
"remediation": null,
"durationMs": 7875
},
"state.rollout_db_parity": {
"id": "state.rollout_db_parity",
"category": "threads",
"status": "warning",
"summary": "rollout files and state DB thread inventory differ",
"details": {
"default model provider": "openai",
"rollout DB active files": "5271",
"rollout DB active rows": "8220",
"rollout DB archive mismatches": "0",
"rollout DB archived files": "0",
"rollout DB archived rows": "0",
"rollout DB duplicate DB paths": "0",
"rollout DB duplicate rollout thread ids": "0",
"rollout DB malformed file names": "0",
"rollout DB missing active rows": "0",
"rollout DB missing archived rows": "0",
"rollout DB model providers": "openai=8220",
"rollout DB rows": "8220",
"rollout DB scan cap reached": "false",
"rollout DB scan errors": "0",
"rollout DB sources": "subagent:thread_spawn=6838, cli=1300, exec=58, subagent:review=17, vscode=7",
"rollout DB stale row sample": [
"/Users/shaswat/.codex/sessions/2025/09/23/rollout-2025-09-23T12-24-04-c4679dde-dc1d-42f2-9613-ab0f2524fe4c.jsonl",
"/Users/shaswat/.codex/sessions/2025/09/23/rollout-2025-09-23T12-24-31-01997808-e0ca-7a80-8e7a-813cdd935e16.jsonl",
"/Users/shaswat/.codex/sessions/2025/09/23/rollout-2025-09-23T12-25-33-01997809-d148-7883-82c9-283b5cc83c66.jsonl",
"/Users/shaswat/.codex/sessions/2025/09/24/rollout-2025-09-24T11-38-02-01997d04-ad27-7d40-9c5e-a27779e946e1.jsonl",
"/Users/shaswat/.codex/sessions/2025/09/24/rollout-2025-09-24T18-55-39-01997e95-5414-76d1-acaf-79d52ac01763.jsonl"
],
"rollout DB stale rows": "2949"
},
"issues": [
{
"severity": "warning",
"cause": "state DB rows point at missing or unusable rollout files",
"measured": "2949 stale rows",
"expected": "every state DB rollout path is a file on disk",
"remedy": null,
"fields": []
}
],
"remediation": null,
"durationMs": 3420
},
"system.disk": {
"id": "system.disk",
"category": "disk",
"status": "ok",
"summary": "sufficient free disk space (618.7 GiB)",
"details": {
"CODEX_HOME available": "618.7 GiB",
"failure threshold": "1.0 GiB",
"warning threshold": "5.0 GiB",
"worktree available": "618.7 GiB"
},
"remediation": null,
"durationMs": 0
},
"system.environment": {
"id": "system.environment",
"category": "system",
"status": "ok",
"summary": "OS language en-US",
"details": {
"EDITOR": "set",
"LANG": "en_US.UTF-8",
"LESS": "set",
"PAGER": "set",
"VISUAL": "set",
"os": "Mac OS 26.5.1 [64-bit]",
"os language": "en-US",
"os type": "Mac OS",
"os version": "26.5.1"
},
"remediation": null,
"durationMs": 6
},
"terminal.env": {
"id": "terminal.env",
"category": "terminal",
"status": "ok",
"summary": "terminal metadata was detected",
"details": {
"COLORTERM": "truecolor",
"TERM_PROGRAM": "Orca",
"color output": "disabled (stdout is not a terminal)",
"effective locale": "en_US.UTF-8",
"stderr is terminal": "false",
"stdin is terminal": "false",
"stdout is terminal": "false",
"terminal": "unknown",
"terminal size": "80x24",
"terminal version": "1.4.190"
},
"remediation": null,
"durationMs": 8
},
"terminal.title": {
"id": "terminal.title",
"category": "title",
"status": "ok",
"summary": "terminal title default",
"details": {
"terminal title activity": "true",
"terminal title items": "activity, project-name",
"terminal title project source": "cwd",
"terminal title project value": ".codex",
"terminal title source": "default"
},
"remediation": null,
"durationMs": 0
},
"updates.status": {
"id": "updates.status",
"category": "updates",
"status": "ok",
"summary": "update configuration is locally consistent",
"details": {
"cached latest version": "0.150.1",
"check for update on startup": "true",
"last checked at": "2026-08-27T17:26:50.054342Z",
"latest version": "0.150.1",
"latest version status": "current version is not older",
"npm update target": "/opt/homebrew/lib/node_modules/@openai/codex",
"update action": "npm install -g @openai/codex",
"version cache": "/Users/shaswat/.codex/version.json"
},
"remediation": null,
"durationMs": 286
}
}
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing how event_msg/item_completed CommandExecution records are written to ~/.codex/sessions and how commandExecution rows are populated in thread_history_1.sqlite. Use the supplied Python measurement script and SQLite query to establish the duplicate fields and projection size. Done means command output is no longer redundantly persisted while rollout offsets and the expected model-visible output remain usable.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- cli, databases, performance
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100