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
Codex Desktop on macOS is continuously writing high-frequency TRACE-level logs into ~/.codex/logs_2.sqlite, including websocket/SSE frame-level logs. This creates sustained SQLite/WAL writes during normal app usage.
Environment
App: Codex Desktop for macOS
Version: 26.623.101652
Bundle build: previously observed 4674
macOS: 26.4.1 (25E253)
Date observed: 2026-07-04
Observed behavior
~/.codex/logs_2.sqlite receives a large volume of TRACE rows during normal interactive use. The related WAL file also remains active.
Confirmed via launchctl print gui/<uid> that RUST_LOG => warn was present. After restarting Codex, new processes still wrote TRACE rows to logs_2.sqlite, so this logging path appears not to be fully controlled by RUST_LOG.
Expected behavior
Normal Codex Desktop usage should not persist high-frequency TRACE websocket/SSE frame logs to ~/.codex/logs_2.sqlite unless an explicit diagnostic mode is enabled. A user-facing or documented way to disable this feedback/trace SQLite logging would also resolve the issue.
Impact
The app performs sustained local disk writes to SQLite/WAL files during normal use. This can cause unnecessary disk churn and makes ~/.codex/logs_2.sqlite grow with low-level TRACE entries.
Representative repeated TRACE fragments include low-level websocket/SSE style entries such as tokio-tungstenite, WouldBlock, Opcode: Data(Text), and app-server delta events. I am intentionally not pasting raw feedback_log_body payloads because the SQLite log DB may contain private session text, local paths, or tool output.
This looks like the same class of issue as described here: even on 26.623.101652 / 0.142.5, the persistent SQLite sink is still receiving high-volume TRACE rows, and the WAL/main DB mtimes continue to advance during normal use.
Root cause analysis, reproduced against current main (f1affbac5e), macOS.
Two callsites feed the write volume:
http-client/src/transport.rs:99,131 — the transport logs the full request body at TRACE on every model call. That body is the entire prompt context, tool outputs and images included.
codex-api/src/sse/responses.rs:530 — the SSE parser logs every streamed event's payload at TRACE, one row per delta while a turn streams.
Both sit behind enabled!(Level::TRACE), written on the assumption that TRACE is off in production. But the SQLite sink installed by both the TUI and the app-server (log_db::default_filter()) persists TRACE for all targets, so the guard always passes. This is also why RUST_LOG=warn doesn't help (#29463): RUST_LOG only filters the stderr layer; the sink has its own filter.
Numbers from my ~/.codex: 87% of retained log content (33 of 38 MiB) is the transport target alone (codex_client::transport in the shipped build, codex_http_client::transport since #31323), with single rows up to 4 MiB. One such row eats most of the 10 MiB per-partition retention budget in a single write and evicts everything else — so this also degrades what /feedback can attach, and it means full prompt content sits unencrypted in logs_2.sqlite.
Separate but related: the logs db is opened with auto_vacuum=INCREMENTAL, but nothing ever runs PRAGMA incremental_vacuum, so pages freed by retention/pruning stay on the freelist and the file never shrinks below its high-water mark. My logs_2.sqlite is 72 MB with 42% of its pages (30 MB) on the freelist.
cap codex_http_client::transport and codex_api::sse at DEBUG in log_db::default_filter(). With no sink enabling those targets at TRACE, the callsites are disabled outright, so the multi-MB serialization is skipped too. RUST_LOG=trace on stderr keeps working for local debugging.
run a capped PRAGMA incremental_vacuum(2048) from logs startup maintenance, between the retention delete and the passive checkpoint.
Both implemented and tested (just test -p codex-state, 153 passed):
I reproduced this on macOS 15.3.1 with ChatGPT Desktop 26.715.21425 / bundled codex-cli 0.145.0-alpha.18.
As a temporary local mitigation (not an upstream fix), I tested a SQLite BEFORE INSERT trigger that drops only TRACE rows while retaining DEBUG, INFO, WARN, and ERROR.
Before applying it:
sqlite_sequence delta: +119 IDs / 10 s
last 60 s: 936 TRACE rows, 3.82 MiB estimated
After applying it during normal interactive use:
sqlite_sequence delta: +13 IDs / 15 s
new rows: INFO 7, DEBUG 5, WARN 1, TRACE 0
codex process-reported bytes written: 352,256 / 15 s
TRACE rows in a subsequent 5-minute check: 0
A previous active-use sample on the same process was about 12.44 MiB / 20 s, so the observed write volume fell by roughly 96%. The app continued to operate normally in this session.
Workaround
Back up the live DB first using SQLite's backup API. Fully quitting Codex first is preferable, but the commands below also use a busy timeout:
sqlite3 ~/.codex/logs_2.sqlite \
".timeout 10000" \
"BEGIN IMMEDIATE; CREATE TRIGGER IF NOT EXISTS codex_local_drop_trace_logs BEFORE INSERT ON logs WHEN NEW.level = 'TRACE' BEGIN SELECT RAISE(IGNORE); END; COMMIT;"
Verify:
sqlite3 ~/.codex/logs_2.sqlite \
"SELECT type, name FROM sqlite_master WHERE type = 'trigger' AND name = 'codex_local_drop_trace_logs';"
This is an unofficial mitigation and may disappear if a migration/update recreates the logs table or database.
It intentionally removes TRACE diagnostics, so users should roll it back before collecting a diagnostic reproduction if maintainers request TRACE data.
It stops the SQLite row writes, but it does not prevent the application from constructing the TRACE event before the insert attempt. The proper upstream fix is still to filter the high-volume targets before serialization/enqueueing.
I did not delete old rows or VACUUM the live DB. Existing file size is separate from ongoing write churn, and VACUUM would cause a large one-time rewrite.
Avoid posting raw feedback_log_body contents; transport rows can contain prompt context, local paths, and tool output.
This may be useful as a stopgap for affected users while the target-level filtering and incremental-vacuum fixes discussed above are reviewed.
Activity
github-actions commentedon Jul 4, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
ballack311 commentedon Jul 4, 2026
Sorkai commentedon Jul 5, 2026
I can reproduce the same issue on macOS with the current stable desktop build.
Environment:
26.623.1016524674codex-cli 0.142.526.5.2(25F84),arm64/Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled~/.codex/logs_2.sqliteRead-only sampling results. I did not create triggers, run checkpoint/VACUUM, delete rows, or modify the DB.
Earlier 30s sample during normal interactive use:
Fresh 20s follow-up sample:
Representative repeated TRACE fragments include low-level websocket/SSE style entries such as
tokio-tungstenite,WouldBlock,Opcode: Data(Text), and app-server delta events. I am intentionally not pasting rawfeedback_log_bodypayloads because the SQLite log DB may contain private session text, local paths, or tool output.This looks like the same class of issue as described here: even on
26.623.101652/0.142.5, the persistent SQLite sink is still receiving high-volume TRACE rows, and the WAL/main DB mtimes continue to advance during normal use.Sorkai commentedon Jul 5, 2026
Additional in-app feedback ID for my reproduction above:
019f3132-544b-7a31-b6f6-485d4c9381b2Gloridust commentedon Jul 8, 2026
Root cause analysis, reproduced against current main (
f1affbac5e), macOS.Two callsites feed the write volume:
http-client/src/transport.rs:99,131— the transport logs the full request body at TRACE on every model call. That body is the entire prompt context, tool outputs and images included.codex-api/src/sse/responses.rs:530— the SSE parser logs every streamed event's payload at TRACE, one row per delta while a turn streams.Both sit behind
enabled!(Level::TRACE), written on the assumption that TRACE is off in production. But the SQLite sink installed by both the TUI and the app-server (log_db::default_filter()) persists TRACE for all targets, so the guard always passes. This is also whyRUST_LOG=warndoesn't help (#29463): RUST_LOG only filters the stderr layer; the sink has its own filter.Numbers from my
~/.codex: 87% of retained log content (33 of 38 MiB) is the transport target alone (codex_client::transportin the shipped build,codex_http_client::transportsince #31323), with single rows up to 4 MiB. One such row eats most of the 10 MiB per-partition retention budget in a single write and evicts everything else — so this also degrades what/feedbackcan attach, and it means full prompt content sits unencrypted inlogs_2.sqlite.Separate but related: the logs db is opened with
auto_vacuum=INCREMENTAL, but nothing ever runsPRAGMA incremental_vacuum, so pages freed by retention/pruning stay on the freelist and the file never shrinks below its high-water mark. Mylogs_2.sqliteis 72 MB with 42% of its pages (30 MB) on the freelist.Fixes are small and follow the pattern of #29457 / #26396 / #29599:
codex_http_client::transportandcodex_api::sseat DEBUG inlog_db::default_filter(). With no sink enabling those targets at TRACE, the callsites are disabled outright, so the multi-MB serialization is skipped too.RUST_LOG=traceon stderr keeps working for local debugging.PRAGMA incremental_vacuum(2048)from logs startup maintenance, between the retention delete and the passive checkpoint.Both implemented and tested (
just test -p codex-state, 153 passed):Happy to open PRs if the team wants them.
dreamitclean commentedon Jul 17, 2026
I reproduced this on macOS 15.3.1 with ChatGPT Desktop 26.715.21425 / bundled
codex-cli 0.145.0-alpha.18.As a temporary local mitigation (not an upstream fix), I tested a SQLite
BEFORE INSERTtrigger that drops onlyTRACErows while retainingDEBUG,INFO,WARN, andERROR.Before applying it:
After applying it during normal interactive use:
A previous active-use sample on the same process was about 12.44 MiB / 20 s, so the observed write volume fell by roughly 96%. The app continued to operate normally in this session.
Workaround
Back up the live DB first using SQLite's backup API. Fully quitting Codex first is preferable, but the commands below also use a busy timeout:
Install the filter:
Verify:
Rollback:
Caveats
logstable or database.feedback_log_bodycontents; transport rows can contain prompt context, local paths, and tool output.This may be useful as a stopgap for affected users while the target-level filtering and incremental-vacuum fixes discussed above are reviewed.