Codex desktop writes high-frequency TRACE logs to ~/.codex/logs_2.sqlite despite RUST_LOG=warn
Summary
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.
Recent measurement from the SQLite DB:
latest last_30s_rows last_30s_trace
2026-07-04 07:44:59 1079 1039
Recent process grouping:
process_uuid rows trace_rows first_seen last_seen
pid:4294:472d51df-7c2e-472f-9986-5e0b96eb7e4d 1200 1111 2026-07-04 07:43:01 2026-07-04 07:44:59
Earlier samples showed similar behavior across new app processes after restart, so this does not appear to be just a stale process.
Dominant log sources / content
Top TRACE sources observed include:
tokio-tungstenitecodex_app_server::outgoing_messagecodex_mcp::connection_managercodex_api::sse::responses
Example repeated message fragments include websocket/frame internals such as:
Opcode: Data(Text)First: 11000001Masked: falseWouldBlockreceived frameParsed headers [...]app-server event: item/agentMessage/delta targeted_connections=1
Attempted mitigation
Set macOS user launch environment:
RUST_LOG=warn
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.
6 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
您好 邮件已收到 谢谢
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.Additional in-app feedback ID for my reproduction above:
019f3132-544b-7a31-b6f6-485d4c9381b2Root 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.
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.