Codex SQLite feedback logs can write ~640 TB/year and rapidly consume SSD endurance

Resolved 💬 154 comments Opened Jun 14, 2026 by 1996fanrui Closed Jul 12, 2026
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Update at Jun 23, 2026: the following 3 PRs are merged, it could avoid 85% logs(feedback from my codex), so let me close this issue.

Thanks @jif-oai for the fix.

Simple Workaround from @beskay https://github.com/openai/codex/issues/28224#issuecomment-4778468005

  1. Quit codex
  2. Run this command:
sqlite3 ~/.codex/logs_2.sqlite "CREATE TRIGGER IF NOT EXISTS
  block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE);
  END;"

-------------------------------------------------------

Following is the original issue

Issue

Codex is continuously writing a large amount of data to the local SQLite feedback log database:

  • ~/.codex/logs_2.sqlite
  • ~/.codex/logs_2.sqlite-wal
  • ~/.codex/logs_2.sqlite-shm

On my machine, after about 21 days of uptime, the main SSD has written about 37 TB. Process/file-level checks show Codex SQLite logs are the main continuous writer.

That extrapolates to roughly 640 TB/year. On a 1 TB SSD, that is about 640 full-drive writes per year. Some consumer SSDs are rated around 600 TBW, so this could consume roughly a full drive's warranted write endurance in less than a year.

Evidence1

A later snapshot makes the write amplification easier to see:

| metric | value |
| --- | ---: |
| current logs_2.sqlite file size | 1.2 GiB |
| current retained rows | 506,149 |
| total allocated row ids | 5,543,677,486 |

So the database currently retains only ~0.5M rows, while the SQLite AUTOINCREMENT counter has already advanced past 5.5B ids.

That is roughly a 10,000x gap between retained rows and historical inserted row ids. Even using the current ~1.2 GiB database size as a rough baseline, this points to 10TB+ scale historical log churn, before accounting for WAL, indexes, pruning, checkpoints, page rewrites, and filesystem/device-level write amplification.

Evidence2

Current retained rows in logs_2.sqlite:

| metric | value |
| --- | ---: |
| retained rows | 681,774 |
| estimated retained log content | 1,035.6 MiB |

Level distribution:

| level | estimated MiB | byte % |
| --- | ---: | ---: |
| TRACE | 732.5 | 70.7% |
| INFO | 266.5 | 25.7% |
| DEBUG | 30.6 | 3.0% |
| WARN | 5.9 | 0.6% |

Largest target+level pairs:

| target | level | estimated MiB |
| --- | --- | ---: |
| codex_api::endpoint::responses_websocket | TRACE | 527.4 |
| codex_otel.log_only | INFO | 141.2 |
| codex_otel.trace_safe | INFO | 121.2 |
| log | TRACE | 97.4 |
| codex_client::transport | TRACE | 60.1 |
| codex_core::stream_events_utils | DEBUG | 27.5 |
| codex_api::sse::responses | TRACE | 19.1 |

The top sources are mostly global TRACE logs, mirrored telemetry logs, and raw websocket/SSE payload logging. TRACE alone is about 70.7% of retained bytes. codex_otel.log_only + codex_otel.trace_safe add another 25.3%. Filtering these categories should remove roughly 96% of retained log bytes in this sample without fully disabling feedback logs.

<details>
<summary>Sanitized examples from the most frequent TRACE source: <code>target=log</code></summary>

These are high-frequency retained samples. Raw websocket/SSE payload bodies are intentionally not included because they may contain private conversation content.

128,764x TRACE log: inotify event: ... mask: OPEN, name: Some("ld.so.cache")
 37,982x TRACE log: inotify event: ... mask: OPEN, name: Some("locale.alias")
 23,843x TRACE log: inotify event: ... mask: OPEN, name: Some("passwd")
  3,639x TRACE log: <tokio-tungstenite checkout>/src/compat.rs:131 AllowStd.with_context
  3,505x TRACE log: <tokio-tungstenite checkout>/src/lib.rs:245 WebSocketStream.with_context
  3,362x TRACE log: <tokio-tungstenite checkout>/src/compat.rs:154 Read.read
  3,356x TRACE log: <tokio-tungstenite checkout>/src/compat.rs:157 Read.with_context read -> poll_read
  3,230x TRACE log: <tokio-tungstenite checkout>/src/lib.rs:294 Stream.poll_next
  3,227x TRACE log: <tokio-tungstenite checkout>/src/lib.rs:304 Stream.with_context poll_next -> read()
  3,213x TRACE log: inotify event: ... mask: OPEN, name: Some("nsswitch.conf")
  2,001x TRACE log: WouldBlock
  1,217x TRACE log: Masked: false
  1,169x TRACE log: Opcode: Data(Text)
  1,169x TRACE log: First: 11000001

</details>

<details>
<summary>Sanitized examples from frequent INFO sources</summary>

The dominant INFO sources are mostly repeated OpenTelemetry mirror events. IDs are redacted.

843x INFO codex_client::custom_ca:
  using system root certificates because no CA override environment variable was selected ...

334x INFO codex_otel.trace_safe:
  session_loop{thread_id=<redacted>}:submission_dispatch{otel.name="op.dispatch.user_input" submission.id=<redacted> codex.op="user_input"}:turn{otel.name="session_task.turn" thread.id=<redacted> ...}

333x INFO codex_otel.log_only:
  session_loop{thread_id=<redacted>}:submission_dispatch{otel.name="op.dispatch.user_input" submission.id=<redacted> codex.op="user_input"}:turn{otel.name="session_task.turn" thread.id=<redacted> ...}

332x INFO codex_otel.log_only:
  session_loop{thread_id=<redacted>}:submission_dispatch{otel.name="op.dispatch.user_input_with_turn_context" submission.id=<redacted> codex.op="user_input_with_turn_context"}:turn{otel.name="session_task.turn" thread.id=<redacted> ...}

332x INFO codex_otel.trace_safe:
  session_loop{thread_id=<redacted>}:submission_dispatch{otel.name="op.dispatch.user_input_with_turn_context" submission.id=<redacted> codex.op="user_input_with_turn_context"}:turn{otel.name="session_task.turn" thread.id=<redacted> ...}

</details>

Write amplification

The retained DB size hides the real write volume. In a 15-second sample:

| metric | before | after |
| --- | ---: | ---: |
| retained rows | 681,774 | 681,774 |
| max row id | 5,003,347,015 | 5,003,383,226 |

About 36,211 rows were inserted in 15 seconds, while retained row count stayed flat. This suggests continuous insert-and-prune write amplification: rows are inserted, indexed, written to WAL, then pruned.

Likely cause

The SQLite feedback log sink is installed with a global TRACE default:

Targets::new().with_default(Level::TRACE)

This persists all targets at TRACE level by default, including dependency/internal logs and large raw protocol payloads.

Proposed fix

Keep feedback logs enabled, but narrow what is persisted by default:

  1. Do not use global TRACE for the SQLite feedback log sink.
  2. Drop or raise thresholds for low-value dependency noise, especially target=log, hyper_util, tokio-tungstenite internals, inotify spam, and low-level OpenTelemetry SDK logs.
  3. Avoid persisting full raw websocket/SSE payloads by default. Store summaries instead: event kind, duration, success/error, token usage, and payload byte length.
  4. Avoid persisting mirrored codex_otel.log_only / codex_otel.trace_safe events unless they are explicitly useful for feedback debugging.
  5. Add a global logs DB size/write cap. Per-thread caps are not enough when many threads/processes exist.

An optional escape hatch such as sqlite_logs_enabled = false would still be useful, but the main fix should be better default filtering.

Related issues and discussions

View original on GitHub ↗

154 Comments

github-actions[bot] contributor · 1 month ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #26869
  • #27741
  • #28166

Powered by Codex Action

1996fanrui · 1 month ago

cc @charley-oai @jif-oai since #12969 introduced the SQLite feedback log sink at TRACE level.

I pushed a minimal branch here:
https://github.com/1996fanrui/codex/tree/codex/reduce-sqlite-feedback-log-writes

The proposed patch does not disable feedback logs. It only changes the SQLite feedback log sink from global TRACE to this default filter:

warn,codex_api=info,codex_app_server=info,codex_core=info,codex_mcp=info,codex_state=info,codex_tui=info

In my local sample, the retained SQLite log content was mostly TRACE plus codex_otel INFO mirror rows, so this should drop the high-volume low-signal writes while keeping INFO+ logs for core Codex targets.

I tried to open an upstream PR, but GitHub returned:

GraphQL: 1996fanrui does not have the correct permissions to execute CreatePullRequest

Other possible fixes if the default-filter approach is not the preferred direction:

  • add a config escape hatch, for example sqlite_logs_enabled = false or a feedback-log-specific filter setting;
  • keep SQLite feedback logs opt-in or sampled instead of always-on at TRACE;
  • truncate or summarize large websocket/SSE/raw event payloads before storing them;
  • enforce a global write/size budget in addition to per-thread retention.
ZenulAbidin · 1 month ago

This is a very serious bug, as it can not only cause login failures on Linux boxes if the system is rebooted while the disk is full, but Codex in /goal mode will actively delete files and folders on your disk in a vain attempt to gain disk space. So the severity of this issue needs to be increased as data loss is possible when using Codex in this way.

jordanade · 1 month ago

WTF this is ridiculous. PLEASE have more respect for your users.

Nicolas0315 · 1 month ago

I prepared a PR-ready branch against current main, but upstream PR creation is still blocked for my account with:

GraphQL: Nicolas0315 does not have the correct permissions to execute `CreatePullRequest`

Branch:
https://github.com/Nicolas0315/codex/tree/codex/log-db-default-filter

Compare:
https://github.com/openai/codex/compare/main...Nicolas0315:codex/log-db-default-filter?expand=1

Proposed PR title:

[codex] Limit persisted SQLite log verbosity

Proposed PR body, following the recent Codex PR format:

Why

logs_2.sqlite is currently attached with a global TRACE filter in both the app server and TUI. On my Windows Codex app install, that persisted high-volume websocket internals and OTel mirror records into the diagnostic SQLite log DB:

  • .codex/logs_2.sqlite: 729,575,424 bytes
  • .codex/logs_2.sqlite-wal: 39,255,392 bytes
  • retained rows: 269,141
  • TRACE: 123,026 rows / 269,065,577 estimated bytes
  • top persisted targets included codex_api::endpoint::responses_websocket, codex_otel.log_only, codex_otel.trace_safe, and log

This matches the write-amplification report here and the Desktop launch failure mode in #27741. The change keeps diagnostic logs enabled, but makes the persisted SQLite sink less noisy by default.

What changed

  • Add a shared log_db::default_filter() for the persistent SQLite log sink.
  • Persist INFO+ by default instead of TRACE.
  • Raise known high-volume mirror/dependency targets to WARN+:
  • codex_otel.log_only
  • codex_otel.trace_safe
  • hyper_util::client::legacy::*
  • log
  • opentelemetry_sdk
  • Use the shared filter from both app-server and TUI log DB setup.
  • Update the SQLite sink filter test to cover retained Codex INFO logs and dropped low-value OTel/mirror logs.

Validation

  • git diff --check
  • just test -p codex-state sqlite_sink_default_filter_drops_low_value_logs
  • just test -p codex-tui sqlite_sink_default_filter_drops_low_value_logs compiled the package, then exited with no matching tests.
  • just fmt completed Rust/Python formatting, then failed on Windows in the Bazel buildifier step with [WinError 2].
  • A broader just test -p codex-app-server run compiled and executed tests but timed out after reporting 831 passed, 4 failed, 6 skipped; the visible failures appeared unrelated to this log filtering change, including a skills fixture warning count mismatch.
ZenulAbidin · 1 month ago

Here I have made two scripts which can serve as temporary mitigations for the WAL issue until OpenAI fixes it.

The first script trims the WAL while Codex processes are running:

#!/usr/bin/env bash
# trim-codex-wal.sh
# Trims the logs_2.sqlite and WAL files by truncating the checkpoint database
set -u

CODEX_DIR="${CODEX_HOME:-$HOME/.codex}"
DB="$CODEX_DIR/logs_2.sqlite"
WAL="$CODEX_DIR/logs_2.sqlite-wal"
SHM="$CODEX_DIR/logs_2.sqlite-shm"

bytes() {
  if [ -e "$1" ]; then
    stat -c '%s' "$1" 2>/dev/null || stat -f '%z' "$1" 2>/dev/null || echo 0
  else
    echo 0
  fi
}

human() {
  if command -v numfmt >/dev/null 2>&1; then
    numfmt --to=iec --suffix=B "$1" 2>/dev/null || echo "$1 bytes"
  else
    echo "$1 bytes"
  fi
}

show_sizes() {
  echo
  echo "Codex SQLite files:"
  for f in "$DB" "$WAL" "$SHM"; do
    printf "  %-45s %12s\n" "$f" "$(human "$(bytes "$f")")"
  done
}

echo "[LIVE RUN] Codex WAL truncate only"
echo "Codex dir: $CODEX_DIR"
echo "DB:        $DB"
echo "WAL:       $WAL"
echo "SHM:       $SHM"

if [ ! -f "$DB" ]; then
  echo
  echo "ERROR: Codex DB not found:"
  echo "  $DB"
  exit 1
fi

if ! command -v sqlite3 >/dev/null 2>&1; then
  echo
  echo "ERROR: sqlite3 is not installed or not in PATH."
  exit 1
fi

show_sizes

BEFORE_WAL="$(bytes "$WAL")"

echo
echo "Running SQLite WAL checkpoint/truncate..."
echo "Command:"
echo "  PRAGMA wal_checkpoint(TRUNCATE);"

RESULT="$(
  sqlite3 "$DB" <<'SQL' 2>&1
PRAGMA busy_timeout = 5000;
PRAGMA wal_checkpoint(TRUNCATE);
SQL
)"
RC=$?

AFTER_WAL="$(bytes "$WAL")"

echo
echo "SQLite output:"
if [ -n "$RESULT" ]; then
  printf '%s\n' "$RESULT"
else
  echo "(no output)"
fi

show_sizes

echo
echo "Result:"
echo "  WAL before: $(human "$BEFORE_WAL")"
echo "  WAL after:  $(human "$AFTER_WAL")"

if [ "$RC" -ne 0 ]; then
  echo
  echo "Failed: sqlite3 returned exit code $RC."
  echo "Likely causes: DB is busy, Codex is actively writing, or disk is critically full."
  exit "$RC"
fi

if [ "$AFTER_WAL" -lt "$BEFORE_WAL" ]; then
  echo
  echo "Success: Codex WAL was truncated."
elif [ "$AFTER_WAL" -eq "$BEFORE_WAL" ]; then
  echo
  echo "No size change."
  echo "Possible reasons:"
  echo "  - WAL was already small/checkpointed."
  echo "  - Codex or another process currently has an active transaction."
  echo "  - Codex is still writing and recreated WAL content immediately."
else
  echo
  echo "WAL grew during the run, probably because Codex was actively writing."
fi

echo
echo "Done."

To run as a cron job:

# Run `crontab -e` in your shell and then add the following line at the end
*/15 * * * * /bin/bash "$HOME/trim-codex-wal.sh" > "$HOME/.codex-wal-clean.log" 2>&1

Note that the main table is trimmed, but there might be other tables which still accumulate data over a much slower period of time, requiring the script below.

The second script deletes the log and WAL files and kills all running Codex processes in order to immediately free disk space:

#!/usr/bin/env bash
# fix-codex-wal.sh
# Deletes the logs-2.sqlite* files and sends SIGTERM followed by SIGKILL to all codex-related processes
# in order to close the open file handles which will allow disk space to be freed
set -u

CODEX_DIR="${CODEX_HOME:-$HOME/.codex}"
DB="$CODEX_DIR/logs_2.sqlite"
WAL="$CODEX_DIR/logs_2.sqlite-wal"
SHM="$CODEX_DIR/logs_2.sqlite-shm"

PIDS=""

add_pid() {
  case "${1:-}" in
    ''|*[!0-9]*) return ;;
    "$$") return ;;
  esac

  case " $PIDS " in
    *" $1 "*) ;;
    *) PIDS="$PIDS $1" ;;
  esac
}

bytes() {
  if [ -e "$1" ]; then
    stat -c '%s' "$1" 2>/dev/null || stat -f '%z' "$1" 2>/dev/null || echo 0
  else
    echo 0
  fi
}

human() {
  if command -v numfmt >/dev/null 2>&1; then
    numfmt --to=iec --suffix=B "$1" 2>/dev/null || echo "$1 bytes"
  else
    echo "$1 bytes"
  fi
}

show_sizes() {
  echo
  echo "Codex SQLite files:"
  for f in "$DB" "$WAL" "$SHM"; do
    printf "  %-45s %12s\n" "$f" "$(human "$(bytes "$f")")"
  done
}

show_processes() {
  if [ -z "$(printf '%s' "$PIDS" | tr -d ' ')" ]; then
    echo "None."
    return
  fi

  printf "%-8s %-8s %-6s %-10s %-12s %s\n" "PID" "PPID" "STAT" "TTY" "ELAPSED" "CMD"

  for pid in $PIDS; do
    ps -p "$pid" -o pid=,ppid=,stat=,tty=,etime=,cmd= 2>/dev/null
  done
}

echo "[LIVE RUN] Codex WAL/SHM cleanup"
echo "Codex dir: $CODEX_DIR"
echo "DB:        $DB"
echo "WAL:       $WAL"
echo "SHM:       $SHM"

show_sizes

echo
echo "[1/5] Finding Codex-related processes..."

for pid in $(pgrep -u "$USER" -x codex 2>/dev/null); do
  add_pid "$pid"
done

for pid in $(pgrep -u "$USER" -x node 2>/dev/null); do
  cmd="$(ps -p "$pid" -o cmd= 2>/dev/null || true)"

  case "$cmd" in
    *codex*|*.codex*|*openai*codex*)
      add_pid "$pid"
      ;;
  esac
done

echo
echo "[2/5] Finding processes holding Codex WAL/SHM files..."

if command -v lsof >/dev/null 2>&1; then
  for f in "$WAL" "$SHM"; do
    if [ -e "$f" ]; then
      for pid in $(lsof -t -- "$f" 2>/dev/null); do
        add_pid "$pid"
      done
    fi
  done

  for pid in $(
    lsof -nP +L1 2>/dev/null | awk -v dir="$CODEX_DIR" '
      index($0, dir "/logs_2.sqlite-wal") && /deleted/ { print $2 }
      index($0, dir "/logs_2.sqlite-shm") && /deleted/ { print $2 }
    '
  ); do
    add_pid "$pid"
  done
else
  echo "lsof not found; cannot check open/deleted WAL handles."
fi

echo
echo "[3/5] Processes to terminate:"
show_processes

if [ -n "$(printf '%s' "$PIDS" | tr -d ' ')" ]; then
  echo
  echo "Sending SIGTERM..."

  for pid in $PIDS; do
    echo "TERM $pid: $(ps -p "$pid" -o cmd= 2>/dev/null || echo unknown)"
    kill "$pid" 2>/dev/null || true
  done

  sleep 3

  STILL=""

  for pid in $PIDS; do
    if kill -0 "$pid" 2>/dev/null; then
      STILL="$STILL $pid"
    fi
  done

  if [ -n "$(printf '%s' "$STILL" | tr -d ' ')" ]; then
    echo
    echo "Some processes survived SIGTERM; sending SIGKILL:"

    printf "%-8s %-8s %-6s %-10s %-12s %s\n" "PID" "PPID" "STAT" "TTY" "ELAPSED" "CMD"
    for pid in $STILL; do
      ps -p "$pid" -o pid=,ppid=,stat=,tty=,etime=,cmd= 2>/dev/null || true
    done

    for pid in $STILL; do
      echo "KILL $pid: $(ps -p "$pid" -o cmd= 2>/dev/null || echo unknown)"
      kill -9 "$pid" 2>/dev/null || true
    done
  else
    echo "All targeted processes exited after SIGTERM."
  fi
fi

echo
echo "[4/5] Deleting Codex WAL/SHM files..."

if [ -e "$WAL" ]; then
  echo "Deleting: $WAL"
  rm -f -- "$WAL"
else
  echo "Not found: $WAL"
fi

if [ -e "$SHM" ]; then
  echo "Deleting: $SHM"
  rm -f -- "$SHM"
else
  echo "Not found: $SHM"
fi

show_sizes

echo
echo "[5/5] Final cleanup check..."

if command -v lsof >/dev/null 2>&1; then
  echo "Remaining deleted Codex files still held open, if any:"
  lsof -nP +L1 2>/dev/null | grep -F "$CODEX_DIR" || echo "None found."
else
  echo "lsof not found; skipping deleted-handle check."
fi

echo
echo "Disk usage:"
df -h "$HOME" 2>/dev/null || true
du -xsh "$CODEX_DIR" 2>/dev/null || true

echo
echo "Done."
Gerry9000 · 1 month ago

plz fix

prodan-s · 1 month ago

Additional macOS / codex-cli 0.141.0 evidence from a local audit, checked again on 2026-06-20 UTC:

  • Current openai/codex main still attaches the SQLite log DB layer with Targets::new().with_default(Level::TRACE) in both app-server and TUI. The two public branches that switch to a lower persisted-log default both still apply cleanly to current main.
  • Local live DB: ~/.codex/logs_2.sqlite is 2.2 GiB, with ~74% freelist/free pages and no triggers. PRAGMA auto_vacuum is already INCREMENTAL on this DB, which is helpful for new maintenance behavior but did not shrink the existing old file; existing installs still need a safe compaction/quarantine path.
  • Representative active Desktop/app-server sample over 20 seconds, while no separate Codex CLI job was started by the probe: MAX(id) advanced by 30,759. Retained rows in that interval were dominated by TRACE/INFO from codex_api::endpoint::responses_websocket, codex_api::sse::responses, codex_otel.log_only, codex_otel.trace_safe, and raw log.
  • A small codex exec --ephemeral --sandbox read-only prompt returning OK advanced MAX(id) by 46,795 over ~21 seconds. Retained rows again concentrated in websocket/SSE TRACE and OTel mirror INFO rows.

This supports the same conclusion as the existing reports: visible DB size is not the whole cost, because high insert/prune churn continues even when file size is stable or mostly freelist. A SQL trigger can reduce stored rows, but it happens after event formatting/queueing and is therefore only a stopgap.

Low-regression fix shape I would recommend:

  1. Change the persisted SQLite layer default before formatting/queueing, not only at insert time: keep WARN+ globally and INFO for core Codex targets, but drop TRACE/DEBUG and noisy mirror/raw targets from the persistent DB by default.
  2. Preserve a way to collect high-detail diagnostic traces for intentional feedback submissions: opt-in per-report ring buffer, short-lived verbose mode, or explicit config. The always-on persistent DB should not be the high-volume trace sink.
  3. Add a documented local config/kill-switch for the SQLite diagnostic log sink or its level/filter.
  4. Add global DB/WAL budgets, safe checkpoint/truncate behavior, stale-reader recovery, and corrupt-log-DB quarantine/recreate behavior.
  5. Include an existing-install cleanup path: incremental auto-vacuum alone does not reclaim old freelist-heavy DBs without enough incremental vacuum work, and older DBs may need a closed-DB VACUUM/recreate/move-aside path.
Necmttn · 29 days ago

The log DB should have an explicit byte budget and retention policy. Durable usage/diagnostic events can stay queryable, while websocket/SSE TRACE payloads need sampling, level caps, and rotation independent of the state DB. A startup receipt with effective log level, DB byte cap, WAL checkpoint policy, and dropped-row count would make write pressure visible before SSD wear becomes the signal.

---

_Generated with ax._

Nicolas0315 · 29 days ago

Additional native Windows check against current main:

  • Upstream source inspected: 63f009e9dad2e70454b7ed6434d8aa28dfb52b51
  • Windows 11 Pro 10.0.26200
  • Codex Desktop active during the sample

Current source still attaches the persistent SQLite log layer with a global TRACE filter in both app-server and TUI:

  • codex-rs/app-server/src/lib.rs
  • log_db::start
  • .with_filter(Targets::new().with_default(Level::TRACE))
  • codex-rs/tui/src/lib.rs
  • log_db::start
  • .with_filter(Targets::new().with_default(Level::TRACE))

I also checked the current log sink implementation:

  • codex-rs/state/src/log_db.rs
  • events are formatted into feedback_log_body, queued, then inserted by a background task.
  • the sink drops low-level opentelemetry_sdk TRACE/DEBUG, which helps one specific noisy target.
  • codex-rs/state/src/runtime/logs.rs
  • each batch inserts rows, then prunes inside the same transaction.

That means DB size or retained row count can stay stable while insert/prune churn continues.

Local aggregate-only measurement from .codex\logs_2.sqlite over 15 seconds, without reading log bodies:

  • retained rows before: 56,766
  • retained rows after: 56,766
  • retained row delta: 0
  • MAX(id) before: 191,131,613
  • MAX(id) after: 191,131,862
  • MAX(id) delta: 249 rows inserted in 15s
  • retained estimated bytes delta: +135,974 bytes

Top retained target+level groups in this Windows sample were still dominated by the same categories already reported here:

  • codex_api::endpoint::responses_websocket TRACE
  • codex_otel.log_only INFO
  • codex_otel.trace_safe INFO
  • codex_core::stream_events_utils INFO
  • codex_client::transport TRACE
  • rmcp::service TRACE
  • codex_api::sse::responses TRACE
  • log TRACE

This reinforces the earlier point: visible DB growth is not a reliable proxy for write pressure. Even with a stable retained row count, the append-and-prune path is still doing work. The low-risk patch shape still looks like filtering before formatting/queueing/inserting, not only pruning after insert.

I also rebuilt a narrow local candidate patch against the current origin/main head:

  • base: f774455 (code-mode: linearize cell terminal state (#29286))
  • adds codex_state::log_db::default_filter()
  • uses the shared filter from app-server and TUI instead of Targets::new().with_default(Level::TRACE)
  • keeps global WARN+
  • keeps INFO+ for core Codex targets (codex_api, codex_app_server, codex_core, codex_mcp, codex_state, codex_tui)
  • raises known high-volume mirror/dependency targets to WARN+ (codex_otel.log_only, codex_otel.trace_safe, hyper_util, log, opentelemetry_sdk)

Local validation:

  • cargo test -p codex-state sqlite_sink_default_filter_drops_low_value_logs -> 1 passed
  • cargo test -p codex-state log_db -> 7 passed
  • cargo check -p codex-app-server -p codex-tui -> passed
  • cargo fmt --check -> passed; stable rustfmt emitted repo-config warnings that imports_granularity is nightly-only
  • git diff --check -> passed

Per docs/contributing.md, I am not opening a PR unless invited, but this latest local patch is PR-shaped if maintainers want this direction.

cooltzy · 28 days ago

I hope the developers see this issue.

kernbug · 28 days ago

Shame on such low software quality with such high memory prices and SSD cost...

letanloc1998 · 28 days ago
More AI-generated code doesn't make your team faster. It might actually slow you down
The real bottleneck was never writing code. It's releasing it, debugging it, & keeping it running well
Every AI output has to have a human owner. If you don't want your name on it, it's probably not good work
Quality first, quantity second
MumuTW · 28 days ago

Additional macOS / Codex Desktop data point.

I checked this on my local machine with Codex Desktop running continuously. I did not read feedback_log_body; only aggregate fields were queried.

Environment:

  • macOS
  • Codex Desktop
  • codex-cli 0.142.0-alpha.6
  • DB: ~/.codex/logs_2.sqlite

Before mitigation, the retained row count stayed stable, but MAX(id) kept advancing:

before_rows=79532
after_rows=79532
row_delta=0
before_max_id=55795989
after_max_id=55798753
max_id_delta=2764
sample_window=20s

The retained log distribution was dominated by the same categories reported here:

codex_api::endpoint::responses_websocket TRACE
codex_otel.log_only INFO
codex_otel.trace_safe INFO
log TRACE
codex_api::sse::responses TRACE

As a temporary local mitigation, while keeping Codex running, I added a SQLite BEFORE INSERT trigger to drop the noisiest low-value rows:

CREATE TRIGGER IF NOT EXISTS codex_drop_noisy_logs
BEFORE INSERT ON logs
WHEN
  NEW.level IN ('TRACE', 'DEBUG')
  OR NEW.target IN ('codex_otel.log_only', 'codex_otel.trace_safe', 'log')
  OR NEW.target LIKE 'hyper_util%'
  OR NEW.target LIKE 'opentelemetry_sdk%'
BEGIN
  SELECT RAISE(IGNORE);
END;

After installing the trigger, the same 20-second sample dropped sharply:

after_before_rows=79532
after_after_rows=79532
row_delta=0
after_before_max_id=55798775
after_after_max_id=55798779
max_id_delta=4
sample_window=20s

This is only a workaround. It reduces SQLite insert/prune churn, but it is not a proper fix because Codex may still format/queue log events before SQLite rejects them. The upstream fix should still filter before formatting/queueing/inserting, ideally with a documented config kill-switch or log-level setting for the SQLite diagnostic sink.

lihang8888 · 28 days ago

Here is how I fix it on my Win11 PC:

python -c "import sqlite3, pathlib; p=pathlib.Path.home()/'.codex'/'logs_2.sqlite'; con=sqlite3.connect(p); con.execute('CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;'); con.commit(); con.close(); print(p)"

And to verify it, exe this:

python -c "import sqlite3, pathlib; p=pathlib.Path.home()/'.codex'/'logs_2.sqlite'; con=sqlite3.connect(p); print(con.execute('SELECT name, tbl_name FROM sqlite_master WHERE type=? AND name=?', ('trigger','block_log_inserts')).fetchall()); con.close()"

zhuhaow · 28 days ago

I made a small unofficial tool for running community-found Codex workarounds:

https://codexfixes.com

Run it with:

npx codex-fixes@latest

So we can stop checking issue threads every day for months and just patch the annoying stuff when the community finds a workaround.

elliexcoding · 28 days ago

Would creating a ramdisk and symlinking it there alleviate the issue temporarily?

MumuTW · 28 days ago

@zhuhaow This is a useful idea, but I’d strongly suggest avoiding @latest in the recommended command.

Because this tool runs local code and modifies ~/.codex, the supply-chain risk is higher than a normal informational CLI. A safer default would be to recommend a pinned version, for example:

npx codex-fixes@0.1.5 --dry-run
npx codex-fixes@0.1.5 apply sqlite-feedback-logs
zhuhaow · 28 days ago

Yeah, that is a very fair point, and I did think about this tradeoff.

The reason I’m currently recommending @.***` is that Codex keeps introducing new bugs, and the fixes here are meant to be a moving set of community workarounds. If users pin one version, they still have to keep checking GitHub issues, npm versions, or this repo to know when a new workaround exists, which is exactly the thing I’m trying to avoid.

That said, I agree the supply-chain risk is real, especially because this tool runs locally and touches ~/.codex. Right now I’m using npm provenance / attestations and the release is built through GitHub Actions, but I’m definitely open to better ideas here.

On Jun 22, 2026, at 19:22, MumuTW @.> wrote: MumuTW left a comment (openai/codex#28224) <https://github.com/openai/codex/issues/28224#issuecomment-4767732595> @zhuhaow <https://github.com/zhuhaow> This is a useful idea, but I’d strongly suggest avoiding @latest in the recommended command. Because this tool runs local code and modifies ~/.codex, the supply-chain risk is higher than a normal informational CLI. A safer default would be to recommend a pinned version, for example: npx @. --dry-run npx @.*** apply sqlite-feedback-logs — Reply to this email directly, view it on GitHub <https://github.com/openai/codex/issues/28224?email_source=notifications&email_token=AAJ2MLGRDWZTFSLXOY4XQRD5BEJJHA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTINZWG43TGMRVHE22M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-4767732595>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAJ2MLDRTKK2A5PSZFW3XED5BEJJHAVCNFSNUABFKJSXA33TNF2G64TZHM4TMNJUGE2TMNBZHNEXG43VMU5TINRWGA3TANJQGY32C5QC>. You are receiving this because you were mentioned.
MumuTW · 28 days ago

@elliexcoding A ramdisk could reduce SSD wear by moving the SQLite DB/WAL writes off disk, but it does not reduce the underlying log event churn.

It also requires very careful handling. Since SQLite is in WAL mode, you have to symlink logs_2.sqlite, -wal, and -shm together. More importantly, if Codex is already running, it will keep writing to the original disk files via existing file handles until the process is restarted.

For always-on dev machines, since Codex continuously prunes old rows to cap the DB size at ~228MB, a ramdisk won't necessarily fill up indefinitely. However, it changes the failure mode into wasting RAM capacity and CPU cycles on ghost churn (formatting and syncing hundreds of trace rows/sec in memory) rather than fixing the root cause.

MumuTW · 28 days ago

@zhuhaow
I don’t think @latest is inherently wrong, and I agree the UX is elegant. Floating versions are common in practice, including in company tooling, because they make updates easy.

The distinction I’d draw is between discovery and local mutation.

For discovery, @latest seems acceptable:

npx codex-fixes@latest doctor

Output example:

Found applicable fixes:

1. sqlite-feedback-logs
   Severity: high
   Will add SQLite trigger to ~/.codex/logs_2.sqlite
   Safe command:
   npx codex-fixes@0.1.6 apply sqlite-feedback-logs

2. broken-global-dictation
   Severity: medium
   Will patch keybinding config
   Safe command:
   npx codex-fixes@0.1.6 apply broken-global-dictation

But for applying a workaround that executes local code and modifies ~/.codex, I’d still prefer the docs to recommend a pinned version and explicit issue id:

npx codex-fixes@0.1.6 apply sqlite-feedback-logs

That keeps the “don’t make users track issue threads” benefit, while avoiding the habit of running whatever the newest package version is against a sensitive local directory.
So maybe the default flow could be:

  • Use @latest doctor to discover available fixes.
  • Print the exact pinned version command for each fix.
  • Require explicit issue id and show the SQL/file changes before applying.
zhuhaow · 28 days ago

I would probably change to this behavior,

User install with npx codex-fixes

Every time it runs, it will check if there is a new version, and propose user to update, with the new fixes listed as information.

I also provide document to run it with npx @.***, but do not do it as default recommendation.

On Jun 22, 2026, at 19:33, MumuTW @.> wrote: MumuTW left a comment (openai/codex#28224) <https://github.com/openai/codex/issues/28224#issuecomment-4767811701> @zhuhaow <https://github.com/zhuhaow> I don’t think @latest is inherently wrong, and I agree the UX is elegant. Floating versions are common in practice, including in company tooling, because they make updates easy. The distinction I’d draw is between discovery and local mutation. For discovery, @latest seems acceptable: npx @. doctor But for applying a workaround that executes local code and modifies ~/.codex, I’d still prefer the docs to recommend a pinned version and explicit issue id: npx @.*** apply sqlite-feedback-logs That keeps the “don’t make users track issue threads” benefit, while avoiding the habit of running whatever the newest package version is against a sensitive local directory. So maybe the default flow could be: Use @latest doctor to discover available fixes. Print the exact pinned version command for each fix. Require explicit issue id and show the SQL/file changes before applying. — Reply to this email directly, view it on GitHub <https://github.com/openai/codex/issues/28224?email_source=notifications&email_token=AAJ2MLDE2GYNALROYLWPMAL5BEKRTA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTINZWG44DCMJXGAY2M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-4767811701>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAJ2MLATVRR4T6D2Y3ELXQD5BEKRTAVCNFSNUABFKJSXA33TNF2G64TZHM4TMNJUGE2TMNBZHNEXG43VMU5TINRWGA3TANJQGY32C5QC>. You are receiving this because you were mentioned.
bchewy · 28 days ago

Looks like this is already being worked on here: https://github.com/openai/codex/pull/29432

jomplox · 28 days ago

Additional macOS Desktop reproduction data from an affected machine. This looks like the same insert/prune churn, and the open PR #29432 appears aimed at the dominant hot path in this sample.

Environment:

macOS 26.5.1 build 25F80, arm64
Codex.app 26.616.51431, bundle 4212
codex-cli 0.141.0
app-server command: /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled
app-server uptime at sampling: about 10h 41m
SQLite journal_mode: wal

Visible files at 2026-06-22T13:32Z:

~/.codex total:             4.2G
~/.codex/logs_2.sqlite:     1,244,856,320 bytes (~1.2G)
~/.codex/logs_2.sqlite-wal: 6,122,352 bytes
~/.codex/logs_2.sqlite-shm: 32,768 bytes
APFS data volume free:      453 GiB

lsof showed the live codex app-server process holding the DB, WAL, and SHM files open. I did not find a deleted logs_2 WAL/SHM handle being held open on this machine.

SQLite snapshot:

retained rows:        108,631
MAX(id):              427,582,858
log time range UTC:   2026-06-12 13:41:16 .. 2026-06-22 13:32:56
thread partitions:    167
process UUIDs:        19
SUM(estimated_bytes): 180.7 MiB
page_size:            4096
page_count:           303,920
freelist_count:       224,833

The large freelist_count is notable: 224,833 * 4096 is about 878 MiB of freelist pages, which explains why the DB file is ~1.2G even though retained estimated_bytes is ~181 MiB. The retention delete/prune loop appears to leave a large file behind unless a reclaim operation happens.

Retained rows by level:

TRACE  45,254 rows  105.5 MiB  58.3% estimated bytes
INFO   55,381 rows   70.7 MiB  39.1%
DEBUG   7,247 rows    4.0 MiB   2.2%
WARN      742 rows    0.6 MiB   0.3%
ERROR       7 rows    ~0 MiB

Top retained targets by estimated bytes:

codex_api::endpoint::responses_websocket TRACE  26,524 rows  80.1 MiB  44.3%
codex_otel.log_only                     INFO   26,971 rows  35.7 MiB  19.7%
codex_otel.trace_safe                   INFO   26,915 rows  33.4 MiB  18.4%
rmcp::service                           TRACE     432 rows   8.6 MiB   4.7%
codex_client::transport                 TRACE     101 rows   7.3 MiB   4.1%
codex_mcp::connection_manager           TRACE   4,833 rows   3.8 MiB   2.1%
log                                     TRACE   8,006 rows   2.1 MiB   1.1%
codex_api::sse::responses               TRACE   1,588 rows   1.6 MiB   0.9%
codex_core::stream_events_utils         DEBUG     722 rows   1.4 MiB   0.8%

The top three targets (responses_websocket plus the two codex_otel mirror targets) account for about 82.6% of retained estimated bytes in this sample, so #29432 should remove most of the retained-byte hot path here.

Timed live sample during ordinary Desktop use:

t0 2026-06-22T13:33:56Z  MAX(id)=427,601,650  retained_rows=108,631  db=1,244,856,320  wal=6,122,352
t1 2026-06-22T13:34:26Z  MAX(id)=427,604,651  retained_rows=108,631  db=1,244,856,320  wal=6,122,352

So MAX(id) advanced by 3,001 in 30 seconds while retained row count stayed flat, which is consistent with continuous insert + prune rather than simple growth.

Rows still retained from that 30s window shortly after sampling:

codex_api::endpoint::responses_websocket TRACE  242 rows  966.1 KiB
codex_otel.log_only                     INFO   249 rows  348.5 KiB
codex_otel.trace_safe                   INFO   247 rows  317.8 KiB
codex_api::sse::responses               TRACE  157 rows  159.9 KiB
log                                     TRACE 1008 rows  106.1 KiB
codex_mcp::connection_manager           TRACE   60 rows   49.0 KiB

Useful takeaways from this machine:

  • The issue reproduces on macOS Desktop, not only CLI/Windows/Linux reports.
  • #29432 targets the biggest observed source in this sample.
  • Even after removing successful WebSocket/OTel event records, there are still smaller TRACE-heavy targets (rmcp::service, codex_client::transport, codex_mcp::connection_manager, log, codex_api::sse::responses) that may be worth covering with a SQLite-sink-specific default filter or global write budget.
  • Physical SSD NAND writes were not measured here; this is SQLite/log-table/file-size evidence only.
samahn0601 · 28 days ago

Independent reproduction on Windows 11 (consumer ~600 TBW NVMe), with two additions beyond the write-volume story.

Corroboration: logs_2.sqlite reached ~989 MB; lifetime autoincrement = 43.9M rows in 32 days (~1.35M/day), with TRACE ≈ 74% of bytes, then the codex_otel.log_only / codex_otel.trace_safe INFO mirror — matching @1996fanrui's filter analysis. Biggest byte sources: codex_api::endpoint::responses_websocket and codex_client::transport. Per #17320, RUST_LOG is ignored by this sink (confirmed here as well).

(1) The file never shrinks after retention deletes it. Pruning is time-based (DELETE FROM logs WHERE ts < ?), so ~99.9% of those 43.9M rows are already gone — yet the file stays ~1 GB because 231,446 / 253,293 pages (91.4%) sit on the freelist. auto_vacuum = INCREMENTAL is set on the DB, but PRAGMA incremental_vacuum is apparently never run, so freed pages accumulate instead of being returned. Worth pairing the default-filter change with a periodic incremental_vacuum (or a size cap) — otherwise existing installs stay permanently bloated even after the write rate is fixed.

(2) Verified partial mitigation users can set today: OTEL_TRACES_SAMPLER=always_off is honored by the binary (it's parsed; the valid values are enumerated in the build) and removes the codex_otel trace mirror. It doesn't touch the TRACE bulk (that needs the filter fix / #17320), but it's a safe env-only knob that complements the tmpfs / insert-trigger workarounds above.

_(Observed on a customized build reporting v26.616.x; the affected targets/subsystem are identical to stock Codex and the symptom matches this issue exactly, so sharing as corroboration rather than as authoritative absolute numbers.)_

W944 · 28 days ago

1.5TB/day of useless autodeleted writes is totally unacceptable. Dead macbook after 1 year.

Tried putting those sqlite files immutable but codex won't start without them.
Putting logs_2.sqlite* in a ramdisk works though;

Create a macOS zsh script at ~/.codex/codex-logs-ramdisk.sh that moves only Codex’s local diagnostic log database logs_2.sqlite, logs_2.sqlite-wal, and logs_2.sqlite-shm onto a RAM disk.

Requirements:
- Script commands: install, rollback, status.
- Default Codex home: $HOME/.codex, overridable with CODEX_HOME.
- Default RAM disk name: CodexLogsRAM, mounted at /Volumes/CodexLogsRAM.
- Default size: 1024 MB, overridable with CODEX_LOGS_RAMDISK_MB.
- Use hdiutil attach -nomount ram://... and diskutil erasevolume APFS.
- Carefully trim the hdiutil device output before passing it to diskutil.
- On install:
  - Refuse to run if Codex appears to be running, unless --force is passed.
  - Create/mount the RAM disk.
  - Run sqlite3 logs_2.sqlite 'PRAGMA wal_checkpoint(TRUNCATE);' if the DB exists and is not a symlink.
  - Move existing logs_2.sqlite* files into ~/.codex/logdb-backups/logs_2-before-ramdisk-YYYYMMDDHHMMSS.
  - Record that backup path in ~/.codex/.logs_2_ramdisk_last_backup.
  - Create symlinks from ~/.codex/logs_2.sqlite* to /Volumes/CodexLogsRAM/logs_2.sqlite*.
- On rollback:
  - Refuse if Codex appears to be running, unless --force is passed.
  - Remove the symlinks.
  - Restore logs_2.sqlite* from the recorded/latest backup.
  - Detach the RAM disk if mounted.
- On status:
  - Show Codex home, RAM disk mount status, disk usage if mounted, current logs_2.sqlite* entries, and latest backup.
- Never touch state_5.sqlite, sessions, config files, or any other Codex data.
- Use set -euo pipefail and safe quoting.
- Make the script executable.
- After writing it, run zsh -n on it.
- Do not run install automatically unless I explicitly ask.
quinncomendant · 28 days ago

I’ve confirmed this on my Mac too.

I’m on macOS 15.7.7 with Codex 26.616.51431. ~/.codex/logs_2.sqlite is already 113M, and SQLite shows page_size=4096, page_count=25862, and freelist_count=9152, so there is already a significant amount of free space trapped inside the file.

What convinced me this is the same issue is the row churn: MAX(id)=34,277,360 with only 31,405 retained rows, and over two 60-second samples MAX(id) increased by 3774 and 3385 (~ 60 writes per second) while retained rows stayed roughly flat. That looks like continuous insert-and-prune logging, not normal low-volume diagnostics.

I’m happy to share the exact commands I used if that would help, but the short version is that this reproduces on macOS for me as well.

DmitriyTor · 28 days ago

Looks like I’m affected by this too. During a 1–2 hour Codex session, Activity Monitor showed that the codex process had written about 50 GB of data. That seems unusually high and very similar to the behavior described in this issue.

macOS + Codex app

almanaculum · 28 days ago

Here's a quick fix to prevent the writes. First quit codex, then add a SQLite trigger that silently ignores new inserts into the diagnostic logs table:

DB="$HOME/.codex/logs_2.sqlite"

sqlite3 "$DB" <<'SQL'
CREATE TRIGGER IF NOT EXISTS block_log_inserts
BEFORE INSERT ON logs
BEGIN
  SELECT RAISE(IGNORE);
END;

DELETE FROM logs;
PRAGMA wal_checkpoint(TRUNCATE);
VACUUM;
PRAGMA wal_checkpoint(TRUNCATE);
SQL

After restarting Codex, logs_2.sqlite-wal stopped growing for me, and PRAGMA wal_checkpoint(TRUNCATE); returned 0|0.

To undo,

sqlite3 "$HOME/.codex/logs_2.sqlite" "DROP TRIGGER IF EXISTS block_log_inserts;"
Key-Zzs · 28 days ago

@almanaculum

Small correction: the undo command should probably be:

sqlite3 "$HOME/.codex/logs_2.sqlite" 'DROP TRIGGER IF EXISTS block_log_inserts;'

The backslash form in the comment may confuse shell parsing.

I also have two concerns/questions about this workaround:

  1. VACUUM rewrites the database, so on already-large logs_2.sqlite files it may itself cause a sizable one-time disk write and require enough free temporary space. Do you think the safer recommendation should be to add the trigger first, verify WAL growth stops, and only then optionally run DELETE/VACUUM?
  1. Since this modifies Codex’s private SQLite schema, future Codex updates or migrations might recreate the table, drop the trigger, or behave unexpectedly with the trigger present. Have you tested whether upgrades still work cleanly after applying this?
Here's a quick fix to prevent the writes. First quit codex, then add a SQLite trigger that silently ignores new inserts into the diagnostic logs table: `` DB="$HOME/.codex/logs_2.sqlite" sqlite3 "$DB" <<'SQL' CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END; DELETE FROM logs; PRAGMA wal_checkpoint(TRUNCATE); VACUUM; PRAGMA wal_checkpoint(TRUNCATE); SQL ` After restarting Codex, logs_2.sqlite-wal stopped growing for me, and PRAGMA wal_checkpoint(TRUNCATE); returned 0|0. To undo, ` sqlite3 "$HOME/.codex/logs_2.sqlite" \ "DROP TRIGGER IF EXISTS block_log_inserts;" ``
almanaculum · 28 days ago

@Key-Zzs

Fair points. I copy-pasted, and the \n got lost there, and the DELETE could leave empty pages that the vacuum would end up copying.
Also yes, there's no guarantee that Codex won't clobber the trigger in the next update, or that it may need to be undone before updating.

hanco1 · 28 days ago

Adding a Windows Codex Desktop data point to this issue.

Environment

  • Platform: Windows
  • Codex Desktop package observed from process path: OpenAI.Codex_26.616.6631.0_x64__2p2nqsd0c76g0
  • Active writer process: codex.exe app-server --analytics-default-enabled
  • RUST_LOG in the shell environment: warn
  • ~/.codex/config.toml: no explicit trace, RUST_LOG, or log-level setting found

Files observed

%USERPROFILE%\.codex\logs_2.sqlite        253,726,720 bytes
%USERPROFILE%\.codex\logs_2.sqlite-wal      5,273,632 bytes
%USERPROFILE%\.codex\logs_2.sqlite-shm         32,768 bytes

SQLite metadata:

journal_mode = wal
page_count = 61945
page_size = 4096
freelist_count = 6227

The log table currently retains around 71k rows, while sqlite_sequence for logs is above 18.5 million, which suggests heavy historical insert/prune churn even though the retained row count is much smaller.

Continuous write sampling

Filesystem metadata sampling over 30 seconds showed logs_2.sqlite-wal LastWriteTimeUtc changing about every 2 seconds. The WAL size was sometimes stable, but the timestamp kept advancing, consistent with ongoing WAL writes/checkpoint activity.

A read-only SQLite sample over 30 seconds:

start_id = 18597199
end_id = 18597388
delta_rows = 189

TRACE: 178 rows, 354,918 estimated bytes
INFO:   11 rows,  13,496 estimated bytes

A second read-only verification sample over 10 seconds:

start_id = 18638636
end_id = 18638773
delta_rows = 137

TRACE: 115 rows, 352,448 estimated bytes
INFO:   17 rows,  20,461 estimated bytes
DEBUG:   5 rows,   1,138 estimated bytes

Dominant recent targets

Recent 10-minute distribution:

TRACE: 1812 rows, 4,014,688 estimated bytes
INFO:   941 rows, 1,260,553 estimated bytes
DEBUG:   27 rows,    58,813 estimated bytes
WARN:     2 rows,       482 estimated bytes

Top TRACE targets in that window:

log                                      961 rows,   109,867 estimated bytes
codex_api::endpoint::responses_websocket 437 rows, 3,529,745 estimated bytes
codex_api::sse::responses                302 rows,   304,403 estimated bytes
codex_mcp::connection_manager             60 rows,    56,626 estimated bytes
codex_app_server::outgoing_message         45 rows,     7,695 estimated bytes

Recent target=log TRACE bodies are dominated by low-level websocket/tungstenite entries such as:

tokio-tungstenite ... Stream.poll_next
tokio-tungstenite ... Read.read
tokio-tungstenite ... read -> poll_read
WouldBlock
received frame
Received message Binary Data
Sending pong/close

Some codex_api::endpoint::responses_websocket TRACE entries are large and appear to include raw-ish response payloads such as response.completed data. I am intentionally not pasting raw payload bodies because they may contain private conversation content.

Process mapping

All recent rows in the sampled window used:

process_uuid = pid:76952:<uuid-redacted>

That PID maps to:

"C:\Program Files\WindowsApps\OpenAI.Codex_26.616.6631.0_x64__2p2nqsd0c76g0\app\resources\codex.exe" app-server --analytics-default-enabled

Expected behavior

With RUST_LOG=warn and no explicit trace logging configured, Codex Desktop should not continuously persist dependency/internal TRACE logs and large websocket/SSE payloads into logs_2.sqlite.

Actual behavior

Codex Desktop app-server continuously inserts TRACE rows into logs_2.sqlite/WAL during normal use. The dominant sources are low-level websocket/tungstenite logs plus responses_websocket/SSE traces. This creates sustained local SQLite write activity and potential disk wear/performance impact.

Please treat this as a user-impacting issue. Even if the exact SSD endurance impact varies by machine, users should not have to absorb continuous avoidable disk writes from TRACE/debug logging during normal Codex Desktop use. It would be very helpful for Codex to prioritize stopping this unnecessary local disk write amplification.

Suggested fix

Please avoid a global TRACE default for the SQLite feedback log sink, or at least filter out low-value dependency targets such as target=log, tokio-tungstenite internals, hyper_util, raw websocket/SSE frames, and large raw response payloads. A configurable local opt-out or size/write cap for the SQLite feedback log would also help.

MoonstersWeb3 · 28 days ago

holy f#c% this thread is confusing, so what is the actual fix script or command?

W944 · 28 days ago
holy f#c% this thread is confusing, so what is the actual fix script or command?

There’s no fix yet. Every option is just a temp hack.
If you want it to stop killing your ssd put it in a ramdisk until OpenAI fixed it properly.

r00q · 28 days ago

Why was this closed as completed? @1996fanrui

pwukun · 28 days ago

Post-release macOS data point: I can still reproduce persistent SQLite log churn after upgrading to rust-v0.142.0.

Environment

  • Platform: macOS 26.5.1, arm64
  • Codex app: 26.616.71553, build 4265
  • Embedded CLI: codex-cli 0.142.0
  • PATH CLI: codex-cli 0.142.0
  • App-server process: /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled
  • App-server PID/start: 7763, started Tue Jun 23 10:17:10 2026
  • RUST_LOG: empty
  • ~/.codex/config.toml: [analytics] enabled = false
  • SQLite trigger count: 0
  • logs_2.sqlite journal mode: wal

Files observed

~/.codex/logs_2.sqlite      745,517,056 bytes
~/.codex/logs_2.sqlite-wal    4,890,472 bytes
~/.codex/logs_2.sqlite-shm       32,768 bytes

The active app-server process is holding the DB/WAL/SHM files open.

Read-only 60 second sample

Sample window:

2026-06-23 10:31:28 +0800 to 2026-06-23 10:32:30 +0800

SQLite metadata before/after:

row_count:       200589 -> 200589
max_id:       725786316 -> 725787932
sqlite_seq:   725786316 -> 725787932
max_ts:      1782181888 -> 1782181946

So the retained row count stayed flat, but sqlite_sequence / max_id advanced by 1,616 in about 60 seconds. This looks like continued insert-and-prune churn.

File mtimes also moved during the sample:

logs_2.sqlite mtime:     1782181886 -> 1782181922
logs_2.sqlite-wal mtime: 1782181888 -> 1782181946

The WAL size stayed stable at about 4.9 MB, so file size alone hides the ongoing write activity.

Recent persisted targets

Rows in the last 60 seconds:

TRACE log                                      964 rows, 175.7 KiB
TRACE codex_app_server::outgoing_message       18 rows,   3.0 KiB
TRACE codex_api::sse::responses                 8 rows,   8.0 KiB
TRACE codex_mcp::connection_manager             6 rows,   4.9 KiB
TRACE codex_api::endpoint::responses_websocket  1 row,   40.9 KiB

TRACE targets in the last 5 minutes:

log                                      972 rows, 184.3 KiB
codex_api::sse::responses                248 rows, 249.3 KiB
codex_mcp::connection_manager             42 rows,  33.6 KiB
codex_app_server::outgoing_message         18 rows,   3.0 KiB
codex_core::session::turn                   6 rows,   5.3 KiB
codex_api::endpoint::responses_websocket    6 rows, 253.7 KiB

Interpretation

#29432 appears to have helped: codex_api::endpoint::responses_websocket is much lower than before.

However, #29457 does not appear to fully solve the persistent SQLite churn in this app build/runtime. The release note says noisy persistent log targets were filtered, and the PR says bridged target=log events should be excluded from the SQLite sink, but TRACE target=log is still the dominant persisted target here.

Expected behavior: after rust-v0.142.0, Codex should not continuously persist high-frequency TRACE target=log rows into logs_2.sqlite by default.

I am intentionally not pasting raw feedback_log_body contents because they may contain private conversation or tool data.

martinmclee · 28 days ago

I can confirm this is still happening on my side (Windows) and looks very similar to what #28224 captured.

My environment:

  • %USERPROFILE% = C:\Users\User
  • Codex CLI: 0.142.0
  • %USERPROFILE%\.codex\logs_2.sqlite: 6,542.19 MB
  • %USERPROFILE%\.codex\logs_2.sqlite-wal: 101.82 MB

DB analysis from my machine:

  • logs table has substantial TRACE/DEBUG activity.
  • Dominant targets are also websocket/SSE/otel/log-style noise channels (similar pattern to the issue body).

CrystalDiskInfo snapshot (CrystalDiskInfo_20260623102934.txt):

  • Health Good (92%), percentage used 8%, no media/data integrity errors.

So this is very consistent with app-level SQLite feedback-log churn rather than SSD health failure.

I also had to apply the known safe non-destructive workaround trigger:
CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;
(no sessions deleted), to keep this from continuing to explode.

Given #28224 is already showing evidence from multiple platforms and now closed after Stop logging every Responses WebSocket event + Filter noisy targets from persistent logs, would be useful to get another Windows data point with these exact local sizes.

HuChundong · 28 days ago

still happen on macos! should reopen this issue

darlingm contributor · 27 days ago

Codex estimated: "this regression plausibly burned low-single-digit millions of dollars of SSD endurance across users during the March-June Window," assuming only 5% of users had the write intensity of the reporter, or: "If affected users averaged 50% of that rate, the estimate rises to the low tens of millions."

@jif-oai Please add a permanent regression test that bounds persistent local writes. Maybe #29432 and #29457 solve this issue, or maybe they don't. But, OpenAI should make sure this never happens again by proactively catching similarly damaging bugs in the future whether it has to do with log writes or anything else.

@jif-oai Please also add regression tests for prioritize reports of runaway CPU/memory/disk usage, across macOS, Windows, and Linux. There are many users experiencing CPU/memory runaway on long ignored bugs, which is unnecessarily burning loads of electricity, degrading battery life, and thermal stress is probably causing some level of hardware damage on some machines unnecessarily constantly running at throttling temperatures.

_Note: Codex's estimate is based on ~ 3 million weekly active Codex users, guessing 50% of them were affected, 95% on SSDs, 75% on the affected version, 5% having the intensity reported here, with $0.13 cost per TBW written to SSDs._

jordanade · 27 days ago

Still seeing ~100KB/sec writes on MacOS using latest version 26.616.71553. I imagine a class-action lawsuit is coming.

W944 · 27 days ago

The most aggravating aspect of this is that this is burnt on useless crap like the /feedback submission data.

Let that be opt in. Disable all that telemetry by default.
Put that app on a diet; it’s so bloated.

Bandersnatch0x · 27 days ago

Still experiencing write amplification on Windows with codex 0.142.0

Environment:

  • Platform: Windows 11 Pro 10.0.26200
  • Codex CLI: 0.142.0
  • Database path: %USERPROFILE%\.codex\logs_2.sqlite

Current database state (2026-06-23 15:17):

  • File size: 2,626 MiB (2.6 GB)
  • Retained rows: 62,020
  • Max rowid: 36,763,864
  • Write amplification: 593x

This means ~36.7 million rows were historically inserted, but 99.8% were pruned, leaving only 62k rows. The continuous insert-then-prune pattern is causing significant SSD write amplification.

The two merged PRs (#29432, #29457) may have reduced log volume, but the core write amplification issue persists on my system after upgrading to 0.142.0.

W944 · 27 days ago
Note: Codex's estimate is based on ~ 3 million weekly active Codex users, guessing 50% of them were affected, 95% on SSDs, 75% on the affected version, 5% having the intensity reported here, with $0.13 cost per TBW written to SSDs.

And just on that damage part - many devices cannot just replace the disk as it's soldered, so the lost value isn't merely ``WASTED_TB_written × (SSD_cost / SSD_endurance_TBW)`, but instead need to use `COMPUTER_cost`` in that formula as it's a non-replaceable component.

dnhkng · 27 days ago

https://github.com/openai/codex/releases/tag/rust-v0.142.0 has the fix in the "Chores" section... 👀

DmitriyTor · 27 days ago

I’m still seeing significant SQLite log churn on macOS with Codex 26.616.71553.

In a ~10 minute sample:

  • retained rows went from 224,960 to 226,514 (+1,554)
  • max rowid went from 340,524,475 to 340,670,459 (+145,984)

So Codex appears to have inserted around 146k rows in ~10 minutes, while only ~1.5k rows were retained. That looks like the same insert-and-prune write amplification pattern described in this issue, although at a lower rate than the original report.

Bandersnatch0x · 27 days ago

Windows PowerShell One-Line Fix

For Windows users, here's a cleaner PowerShell version of the temporary fix.

Environment Verification

I can confirm this issue still exists on Windows 11 (even after rust-v0.142.0):

  • Platform: Windows 11 Pro 10.0.26200
  • Codex CLI: 0.142.0
  • Log file size: logs_2.sqlite = 2.6 GB
  • Write amplification: Database retains ~62k rows, but historical max_id has reached 36M+ (~593x amplification)

Quick Fix (Recommended)

Add trigger to block writes

sqlite3 "$env:USERPROFILE\.codex\logs_2.sqlite" "CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;"

Verify trigger is installed

sqlite3 "$env:USERPROFILE\.codex\logs_2.sqlite" "SELECT name FROM sqlite_master WHERE type='trigger' AND name='block_log_inserts';"

Expected output:

block_log_inserts

Undo the fix (restore logging)

If you need to restore Codex's logging functionality:

sqlite3 "$env:USERPROFILE\.codex\logs_2.sqlite" "DROP TRIGGER IF EXISTS block_log_inserts;"

Advantages

Compared to other community solutions:

  1. Safer: No DELETE or VACUUM, avoiding potential extra disk writes from rewriting large files
  2. No need to stop Codex: Can be executed while Codex is running
  3. Reversible: Clear undo command provided
  4. Simple: Single-line command, easy to copy-paste

Important Notes

  • ⚠️ This is a temporary workaround, not an official fix
  • ⚠️ This operation blocks all new log writes to logs_2.sqlite
  • ⚠️ Future Codex updates may rebuild the table or remove the trigger
  • ℹ️ If you need to submit a feedback report, remember to undo the trigger first to restore logging functionality

Optional: Clean Up Existing Logs (Advanced Users)

If you want to reclaim disk space, after adding the trigger you can execute cleanup (requires stopping all Codex processes first):

# Stop all Codex processes
Get-Process claude -ErrorAction SilentlyContinue | Stop-Process -Force

# Clean and compress the database
$db = "$env:USERPROFILE\.codex\logs_2.sqlite"
sqlite3 $db "DELETE FROM logs; PRAGMA wal_checkpoint(TRUNCATE); VACUUM; PRAGMA wal_checkpoint(TRUNCATE);"

⚠️ Warning: The VACUUM operation rewrites the entire database file, which may take considerable time on large files and produce a one-time large amount of disk writes.

Verify Effectiveness

After applying the fix, you can verify writes have stopped:

# Record current max_id
$before = sqlite3 "$env:USERPROFILE\.codex\logs_2.sqlite" "SELECT MAX(id) FROM logs;"
Write-Output "Before: $before"

# Wait 60 seconds
Start-Sleep -Seconds 60

# Check max_id again
$after = sqlite3 "$env:USERPROFILE\.codex\logs_2.sqlite" "SELECT MAX(id) FROM logs;"
Write-Output "After: $after"
Write-Output "Delta: $($after - $before)"

If the trigger is working, Delta should be 0 or close to 0.

---

Hope this helps other Windows users!

cyfung1031 · 27 days ago

just link two more open issues for visitors coming here:

~/Library/Application Support/com.openai.codex/web/Crashpad/pending

com.openai.codex.code_sign_clone

1996fanrui · 27 days ago
I’m still seeing significant SQLite log churn on macOS with Codex 26.616.71553. In a ~10 minute sample: retained rows went from 224,960 to 226,514 (+1,554) max rowid went from 340,524,475 to 340,670,459 (+145,984) So Codex appears to have inserted around 146k rows in ~10 minutes, while only ~1.5k rows were retained. That looks like the same insert-and-prune write amplification pattern described in this issue, although at a lower rate than the original report.

Hey @DmitriyTor , thanks for testing it with the latest version, have you killed all old version codex?

beskay · 27 days ago

This issue is not fixed. On ubuntu, using sudo iotop -aoP I can see that Codex writes around 10MB of data per second, which is around 315TB per year.

I am using version 0.142.0

1996fanrui · 27 days ago
This issue is not fixed. On ubuntu, using sudo iotop -aoP I can see that Codex writes around 10MB of data per second, which is around 315TB per year. I am using version 0.142.0

Hey @beskay , thanks for testing it with the latest version!

Same question with the last one, have you killed all old version codex?

beskay · 27 days ago

Yes I deleted all old versions. The second I send a message to Codex, it starts writing around 10MB/s of data to my disk

edit: To be more clear, I deleted all prior releases in ~/.codex/packages/standalone/releases/

DmitriyTor · 27 days ago
> I’m still seeing significant SQLite log churn on macOS with Codex 26.616.71553. > In a ~10 minute sample: > > retained rows went from 224,960 to 226,514 (+1,554) > max rowid went from 340,524,475 to 340,670,459 (+145,984) > > So Codex appears to have inserted around 146k rows in ~10 minutes, while only ~1.5k rows were retained. That looks like the same insert-and-prune write amplification pattern described in this issue, although at a lower rate than the original report. Hey @DmitriyTor , thanks for testing it with the latest version, have you killed all old version codex?

Yes I deleted all old versions. And tested for 20 minutes session second time

In a ~20 minute sample:

  • retained rows went from 226,534 to 230,022 (+3,488)
  • max rowid went from 340,905,545 to 341,368,449 (+462,904)

So Codex appears to have inserted around 463k rows in ~21 minutes while only retaining ~3.5k rows. That is roughly 370 inserted rows/sec
There are also intervals where retained rows stay completely flat while max rowid grows rapidly. For example, around 11:39:48–11:40:13, retained rows stayed at 230,022 while max rowid increased from 341,155,283 to 341,202,311.

beskay · 27 days ago

Quick workaround until it is fixed:

  1. Quit codex
  2. Run this command:
sqlite3 ~/.codex/logs_2.sqlite "CREATE TRIGGER IF NOT EXISTS
  block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE);
  END;"

After this my disk usage went to almost zero. Only ~5mb per minute in read/writes.

Openclaw-Metis · 27 days ago

Still reproducible on Windows Codex Desktop 26.616.9593.0 / CLI 0.142.0

I tested the current Windows Codex Desktop build after the rust-v0.142.0 fixes (#29432 and #29457). The issue appears reduced, but not fully fixed on this environment.

Environment:

  • Platform: Windows
  • Codex Desktop AppX package: OpenAI.Codex_26.616.9593.0_x64__2p2nqsd0c76g0
  • AppX version: 26.616.9593.0
  • Embedded codex.exe binary string contains: 0.142.0
  • Active app-server process: codex.exe app-server --analytics-default-enabled
  • Running Codex processes observed all came from the same OpenAI.Codex_26.616.9593.0 package path
  • No block_log_inserts workaround trigger is installed in logs_2.sqlite

Current DB snapshot:

logs_2.sqlite retained rows: 38,130
min(id): 16,855,928
max(id): 19,493,540
sqlite_sequence: 19,493,540
retained estimated content: 66.41 MiB
first retained ts: 2026-06-17 18:58:32 local
last retained ts: 2026-06-23 18:50:56 local

Retained level distribution:

TRACE 22,859 rows / 50.95 MiB
INFO  11,260 rows / 13.34 MiB
DEBUG  3,604 rows /  1.98 MiB
WARN     391 rows /  0.10 MiB
ERROR     16 rows /  0.03 MiB

Read-only 60 second sample:

Sample: 2026-06-23 18:49:39 to 18:50:39 local
retained row count: 38,130 -> 38,130   (delta 0)
max(id):             19,490,906 -> 19,491,367   (delta 461)
sqlite_sequence:     19,490,906 -> 19,491,367   (delta 461)
max(ts) delta:       54 seconds

Rows inserted during that sample (id > before_max_id):

TRACE 448 rows / 134.1 KiB
DEBUG  10 rows /   3.0 KiB
INFO    2 rows /   0.4 KiB
ERROR   1 row  /   1.6 KiB

Top inserted targets during that sample:

TRACE log                                      424 rows / 129.2 KiB
TRACE codex_app_server::outgoing_message       11 rows /   1.9 KiB
TRACE hyper_util::client::legacy::pool          9 rows /   2.0 KiB
TRACE hyper_util::client::legacy::client        3 rows /   0.7 KiB
TRACE hyper_util::client::legacy::connect::http 1 row  /   0.3 KiB

Interpretation:

  • #29432 appears to reduce the large codex_api::endpoint::responses_websocket payload churn.
  • However, #29457 does not appear to fully exclude bridged target=log events from the persistent SQLite sink in this Windows Desktop runtime.
  • The retained row count stayed flat while max(id) and sqlite_sequence advanced, so insert-and-prune churn is still happening.

I intentionally did not include raw feedback_log_body values because they may contain private conversation or tool data.

darlingm contributor · 27 days ago

Codex analyzed the disk usage and says this bug cost me $38.64 in drive value of my Samsung 990 2TB NVMe. Codex resets would be nice. It would be nice if these ones didn't expire in 30 days though, because I already have 2 banked and probably wouldn't get to use the new ones.

jif-oai contributor · 27 days ago

@Bandersnatch0x I just cut a new alpha with one more patch, could you test it on your machine to see the impact? v0.143.0-alpha.6

neffo · 27 days ago

Please don't change this behavior, this is important to my agent workflows. I have configured my agents use the sector write counts on my Samsung 990 EVO 2tb as an immutable counter as part of their coordination. Changes to this behavior would very likely break this workflow. If this is to be changed please enable a option to re-enable this expected behavior. Thanks.

stanthewizzard · 27 days ago

is this ok with the windows or mac app ?
not just cli ?
Thanks

jif-oai contributor · 27 days ago

It is just an alpha CLI. It will come to the App soon

Yunle-Lee · 27 days ago

awesome

mdbecker · 27 days ago

Just in case this doesn't get fixed with the latest alpha release...

I’m seeing what looks like the same continuing underlying SQLite churn pattern on macOS, though my setup differs slightly: I have not specifically disabled analytics/plugins, and the running process is codex app-server --analytics-default-enabled.

I posted the fuller diagnostic here: https://github.com/openai/codex/issues/29612#issuecomment-4780728496

Summary of my repro:

Codex Desktop: 26.616.71553 • Released Jun 22, 2026
Bundled CLI: codex-cli 0.142.0
Platform: macOS 15.3, Darwin 24.3.0, arm64
Launch method: macOS GUI / Spotlight
Running process: /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled
Actual process env includes: RUST_LOG=warn

Over a 5-minute idle sample, with Codex Desktop open in the background but not actively used:

new_insert_ids=2975
retained_row_delta=0
insert_ids_per_sec=9.92

The DB was healthy and compacted beforehand, so this did not appear to be leftover pre-fix bloat:

quick_check=ok
integrity_check=ok
journal_mode=wal
freelist_count=115
logs_2.sqlite≈63M
logs_2.sqlite-wal≈4.1M

The exact sampled ID window showed retained rows dominated by TRACE target=log:

TRACE log                                998 rows
TRACE hyper_util::client::legacy::pool     2 rows

All retained rows from that sampled window belonged to the same app-server process.

So this looks like the same post-0.142.0 behavior: file size may remain bounded, but the app-server still continuously inserts and prunes persistent SQLite log rows while idle, mostly TRACE target=log, despite RUST_LOG=warn.

jif-oai contributor · 27 days ago

@mdbecker would you mind trying your setup on the alpha?

qq648125180 · 27 days ago

logs_2.sqlite still receives high-frequency TRACE writes on macOS Desktop / CLI 0.130.0 after #28224

I am seeing behavior that appears to match or partially persist after #28224.

Environment

  • OS: macOS 14.6.1 (23G93)
  • Codex CLI: codex-cli 0.130.0
  • Codex Desktop app: 26.616.71553
  • Database path:
  • ~/.codex/logs_2.sqlite
  • ~/.codex/logs_2.sqlite-wal
  • ~/.codex/logs_2.sqlite-shm

Current local database state

File sizes:

logs_2.sqlite      499M
logs_2.sqlite-wal  9.9M
logs_2.sqlite-shm   32K

Schema:

0|id|INTEGER|0||1
1|ts|INTEGER|1||0
2|ts_nanos|INTEGER|1||0
3|level|TEXT|1||0
4|target|TEXT|1||0
5|feedback_log_body|TEXT|0||0
6|module_path|TEXT|0||0
7|file|TEXT|0||0
8|line|INTEGER|0||0
9|thread_id|TEXT|0||0
10|process_uuid|TEXT|0||0
11|estimated_bytes|INTEGER|1|0|0

At one sample point:

max(id) = 54,906,375
retained rows = 27,844

This large gap between max(id) and retained rows looks similar to the insert/prune write amplification described in #28224.

Short sampling evidence

In a 15 second sample during normal active use:

before: max(id)=54,850,074 retained rows=27,649
after:  max(id)=54,854,082 retained rows=27,705
delta:  4,008 inserted ids in 15 seconds, about 267 ids/sec

The retained row count only increased by 56 rows during the same sample.

Level distribution

TRACE | 16,386 | 38.63 MiB
INFO  |  8,534 | 10.22 MiB
DEBUG |  2,711 |  1.79 MiB
WARN  |    179 |  0.15 MiB
ERROR |     17 |  0.01 MiB

Largest target / level pairs

codex_api::endpoint::responses_websocket | TRACE | 3612 | 32.4 MiB
codex_otel.log_only                      | INFO  | 3819 |  4.92 MiB
codex_otel.trace_safe                    | INFO  | 3679 |  4.51 MiB
codex_api::sse::responses                | TRACE | 2570 |  2.56 MiB
log                                      | TRACE | 7785 |  1.98 MiB
codex_core::stream_events_utils          | DEBUG |  557 |  0.89 MiB
rmcp::service                            | TRACE |   24 |  0.71 MiB
codex_mcp::connection_manager            | TRACE |  528 |  0.40 MiB
feedback_tags                            | INFO  |  293 |  0.36 MiB
codex_core::session::handlers            | DEBUG |  168 |  0.32 MiB

Expected behavior

After the fixes referenced in #28224, the persistent SQLite log sink should no longer keep writing high-frequency TRACE logs during normal active use, especially for websocket/SSE response paths.

Actual behavior

~/.codex/logs_2.sqlite still appears to receive hundreds of inserted log rows per second during normal active use, with TRACE and responses_websocket still dominating retained bytes.

Could you confirm whether Codex Desktop app 26.616.71553 / CLI 0.130.0 includes the #28224 fixes, and whether this remaining write rate is expected?

Bandersnatch0x · 27 days ago

@jif-oai Tested v0.143.0-alpha.6 on Windows 11 against 0.142.0 as baseline. Results below.

Test setup

  • Platform: Windows 11 Pro 10.0.26200
  • Provider: custom (wire_api = "responses") — triggers the same WebSocket/SSE code paths
  • Method: Isolated CODEX_HOME per version, identical config.toml and auth.json
  • Workload: codex app-server --listen ws://127.0.0.1:PORT idle for 25 seconds, then killed
  • Measurement: sqlite_sequence, COUNT(*), level/target distribution from logs_2.sqlite

Both test databases were clean before starting (no triggers, no prior rows).

Results (25-second idle sample)

| Metric | 0.142.0 | 0.143.0-alpha.6 |
|--------|---------|-----------------|
| Total rows (seq) | 70 | 70 |
| INFO | 31 | 31 |
| TRACE | 21 | 24 |
| DEBUG | 12 | 10 |
| WARN | 5 | 4 |
| ERROR | 1 | 1 |
| target=log TRACE | 12 (17%) | 0 ✅ |
| codex_otel.* mirror | 0 | 0 |

TRACE breakdown

0.142.0: 12× target=log TRACE + 9× other TRACE
alpha.6: 0× target=log TRACE + 24× hyper_util::client::legacy::pool/client/connect::http TRACE

What this confirms

  1. #29599 works — bridged target=log TRACE events are completely eliminated (12 → 0)
  2. #29432/#29457 still holding — no codex_otel.* mirror rows, no responses_websocket TRACE in either version
  3. ⚠️ hyper_util TRACE noise persists (24 rows) but is much lower volume than the bridge spam was

Caveat

This is an idle test — no actual API calls flowed through the app-server. The real write amplification reported in #28224 happens during active responses_websocket/SSE streaming. pwukun reported ~60% of runtime inserts came from target=log TRACE on 0.142.0; those should all be gone now. Would be great if someone running the Desktop app (where app-server processes live traffic) can confirm the runtime numbers.

Raw verification

# alpha.6 — no target=log TRACE
$ sqlite3 logs_2.sqlite "SELECT COUNT(*) FROM logs WHERE target='log' AND level='TRACE';"
0

# alpha.6 — no OTel mirror
$ sqlite3 logs_2.sqlite "SELECT COUNT(*) FROM logs WHERE target LIKE 'codex_otel%';"
0

# alpha.6 — all TRACE is hyper_util internals
$ sqlite3 logs_2.sqlite "SELECT target, COUNT(*) FROM logs WHERE level='TRACE' GROUP BY target;"
hyper_util::client::legacy::pool|11
hyper_util::client::legacy::client|9
hyper_util::client::legacy::connect::http|4

Happy to re-test with a different workload or provide the raw SQLite dumps.

mdbecker · 27 days ago
@mdbecker would you mind trying your setup on the alpha?

I also tested the 0.143.0-alpha.7 standalone app-server binary, but I do not consider this equivalent to the Desktop GUI repro. In that standalone run, the 5-minute idle sample produced only 45 inserted IDs, or 0.15/sec, and retained rows increased by 45, so I did not observe the same insert/prune churn there. However, I could not test a signed Desktop GUI alpha because the GitHub release asset appears to provide standalone binaries rather than a signed Codex.app bundle.

UPDATE: I also tested the bundled 0.142.0 app-server standalone as a control. It also did not reproduce the GUI idle churn: 69 inserted IDs over 5 minutes, or 0.23/sec, with retained rows increasing by 69. This suggests the ~10/sec insert-then-prune churn I’m seeing is specific to the signed Desktop GUI-launched path, or to something the GUI/frontend causes the app-server to do while idle, rather than the bare app-server process by itself.

UPDATE 2: I also measured the signed 0.142.0 Desktop GUI while it was actively running a task, as a comparison to the idle repro. The churn increased from ~9.92 inserted IDs/sec while idle to ~21.49 inserted IDs/sec during active use. Retained row count still stayed effectively flat, so this continues to look like insert-then-prune churn. The active run also showed a small number of large TRACE transport rows, including codex_client::transport at ~9.8 MiB body_kib across 11 rows. That did not appear in the idle-only sample. This reinforces that the signed Desktop GUI path is still persisting TRACE rows despite RUST_LOG=warn, and active tasks can produce both more rows and much larger persisted bodies.

If you let me know when a signed macOS app is available, I’d be happy to test it and rerun my diagnostics.

xmexg · 27 days ago

Is there a way to create a virtual drive or volume in Windows that acts like /dev/null?
I want any file written to this drive to be automatically discarded (effectively disappearing), so I can redirect my log files there.

W944 · 27 days ago
Is there a way to create a virtual drive or volume in Windows that acts like /dev/null? I want any file written to this drive to be automatically discarded (effectively disappearing), so I can redirect my log files there.

https://www.ltr-data.se/opencode.html/#ImDisk + mklink the sqlite files there

lmtr0 · 27 days ago

@W944 do you know what I can do in linux? Maybe a memory map file? linking /dev/null fails (codex says database got corrupted)

W944 · 27 days ago
@W944 do you know what I can do in linux? Maybe a memory map file? linking /dev/null fails (codex says database got corrupted)

/dev/shm is already a ram-only filesystem on linux, just symlink the 3 sqlite files onto there and you're good to go
/dev/null is a black hole - that's not the destination you want as codex still needs to write/read its crap in those sqlite files. Just that in the ramdisk it's all ephemeral.

Naville · 27 days ago

Glad I don't vibe code much :)

btw is this resolved in the today's desktop app update?

www77413 · 26 days ago

I tested the current stable signed macOS Desktop app after updating and fully restarting Codex. The issue is improved compared with older versions, but high-frequency SQLite log churn is still reproducible during an active Desktop GUI session.

Environment

  • macOS Desktop app: 26.616.81150
  • Bundle build: 4306
  • CLI bundled with the app: codex-cli 0.142.0
  • Log DB: ~/.codex/logs_2.sqlite

30-second active-session sample

Immediately after restart, I sampled max(id) from the logs table:

before: max(id)=19050825 retained_rows=16101
after:  max(id)=19055663 retained_rows=16101
delta:  4,838 inserted ids in 30 seconds, about 161 ids/sec

The retained row count stayed flat, so this still looks like insert-then-prune churn rather than normal retained-log growth.

File sizes at the same time:

logs_2.sqlite      122M
logs_2.sqlite-wal  5.0M
logs_2.sqlite-shm  32K

New rows during the sample

For rows with id > 19050825:

log | TRACE                                | 971
codex_api::sse::responses | TRACE          | 72
codex_app_server::outgoing_message | TRACE | 22
codex_core::stream_events_utils | DEBUG     | 14
codex_mcp::connection_manager | TRACE       | 12
codex_core::stream_events_utils | INFO      | 7
feedback_tags | INFO                       | 6
hyper_util::client::legacy::pool | TRACE    | 5
codex_api::endpoint::responses_websocket | TRACE | 3
codex_core::session::turn | TRACE          | 3

The newest retained rows were still WebSocket internals bridged through target=log, for example:

TRACE | log | decompressing 45 bytes in final frame
TRACE | log | received frame
TRACE | log | Parsed headers [193, 45]
TRACE | log | .../tokio-tungstenite.../src/lib.rs:304 Stream.with_context poll_next -> read()
TRACE | log | .../tokio-tungstenite.../src/lib.rs:294 Stream.poll_next

So on the signed Desktop GUI path, 0.142.0 still appears to persist high-volume target=log TRACE rows during active use. This seems consistent with the expectation that #29599 / the 0.143.0 line should remove the remaining bridged target=log TRACE spam, but it is not fixed in the current stable Desktop build I received today.

wfy-op · 26 days ago

After updating the win-codex version to 81150 today(which winapp version is 26.616.10790.0 ), there are still issues. The self-check conclusion is as follows.

There are changes, but not the kind of changes that "improve to a safe level".
Based on our several rough samplings, it can be roughly calculated as:
| Time/State | max_id Increase | Approximately Equal to | WAL Change | Main Source | |---|---:|---:|---:|---|
| Early update silent | +375 / 31.7s | ~12 lines/s | Size remains unchanged but mtime is updated | tokio-tungstenite TRACE |
| After one time | +442 / 34.5s | ~13 lines/s | Size remains unchanged but mtime is updated | tokio-tungstenite TRACE |
| Peak before/after version/restart | +28,053 / 33.7s | ~832 id/s | +3.88 MB | tokio-tungstenite TRACE |
| After restart | +46,083 / 32.6s | ~1,414 id/s | +5.60 MB | websocket/SSE/TRACE |
| Today's latest | +6,676 / 33.5s | ~199 id/s | WAL size remains unchanged, mtime is updated | codex_app_server::outgoing_message TRACE |
So: Compared to the worst result yesterday, which was +28k to +46k in 30 seconds, this time today it has dropped to +6.7k in 30 seconds. It is indeed much smaller. **

But there are still two issues:
max_id is still increasing, the WAL modification time is still being updated, and TRACE is still being added to the database.

  1. The source of logging has changed, not disappeared

Previously, it was mainly tokio-tungstenite / websocket-based trace; today, it has mainly become:

codex_app_server::outgoing_message
codex_api::sse::responses
codex_api::endpoint::responses_websocket
codex_mcp::connection_manager

The size of the new log entries added today is approximately 1.31 MB / 33.5 seconds, which is about 39 KB/s, equivalent to approximately 3.4 GB/day in terms of log content volume; the actual SSD write speed may be higher or lower due to SQLite/WAL/checkpoint/index factors.
Conclusion: The write volume has significantly decreased compared to yesterday's peak, but the problem has not disappeared. It is still not a normal low-frequency log behavior. **

jessejamesccp · 26 days ago

This is honestly ridiculous. Did the Codex team’s architects or technical owners not consider the system-wide implications of this at all? What level of technical ownership is this?

An architect is supposed to think globally. Things like logging volume, default log levels, disk write amplification, WAL growth, log rotation, retention limits, high-frequency event streams, and long-running workloads should be almost reflexive considerations for experienced backend engineers.

A developer using a shared logging utility may not understand the full impact, but that is exactly why architects and responsible technical owners are supposed to provide guardrails and review the overall design. A shared logging system should make unsafe behavior difficult or impossible by default.

Persistent TRACE-level logging of high-frequency events without strict limits, sampling, rotation, retention controls, or disk-write safeguards is not an obscure edge case. It is a basic operational risk for any developer tool that can run continuously on users’ machines.

Yet this still happened in Codex. It is difficult to understand how this passed architecture review, long-running stress testing, and release quality gates.

If your architects are unable to identify risks like this, I would be happy to help review the design.

barry-hubris · 26 days ago

<img width="2172" height="724" alt="Image" src="https://github.com/user-attachments/assets/15975a0f-6a0c-47bf-b9e6-b42447b074f7" />

daryll-swer · 26 days ago
This is honestly ridiculous. Did the Codex team’s architects or technical owners not consider the system-wide implications of this at all? What level of technical ownership is this?

Probably the AI/LLM did the architecture, reviewed and approved it?

Crear12 · 26 days ago

I can still reproduce significant logs_2.sqlite write amplification on the latest available Codex Desktop build.

Environment:

  • macOS: 26.5.1
  • Codex Desktop: 26.616.81150
  • Codex CLI: 0.142.0
  • Old app-server issue ruled out: I previously had a stale 0.137.0 app-server holding logs_2.sqlite, but it is now gone. Current writer is /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled.

Current DB state:

  • ~/.codex/logs_2.sqlite: 963 MiB
  • ~/.codex/logs_2.sqlite-wal: ~45 MiB
  • WAL mode enabled
  • retained rows: ~145k
  • max row id: ~270M

Recent 15-minute retained log distribution:

  • total: 4,285 rows, 9.81 MiB
  • TRACE: 3,741 rows, 8.8 MiB

Top recent targets:

  • codex_api::endpoint::responses_websocket TRACE: 5.84 MiB
  • codex_api::sse::responses TRACE: 1.8 MiB
  • codex_mcp::connection_manager TRACE: 0.72 MiB
  • codex_core::stream_events_utils DEBUG: 0.42 MiB
  • log TRACE: 0.38 MiB

15-second samples after removing the stale old app-server:

  • sample 1: id_delta=1895, count_delta=-90, wal_delta=0
  • sample 2: id_delta=2420, count_delta=89, wal_delta=0

So 0.142.0 appears to reduce the worst behavior, but it does not fully stop persistent TRACE/streaming/MCP logging. The file size may look stable, but MAX(id) keeps advancing quickly while retained row count stays roughly flat, which still indicates insert-and-prune write amplification.

Expected behavior:

  • high-frequency TRACE/streaming/MCP events should not be persisted by default
  • they should go to an in-memory ring buffer, or be sampled/summarized
  • persistent SQLite logs should be capped and limited to WARN/ERROR, crash context, or explicit feedback capture
chensenxiang · 26 days ago

Still reproducing on 0.142.0 (which already ships #29432 + #29457) — macOS desktop app-server

Environment:

  • macOS (Darwin 25.5.0), Apple Silicon
  • SSD: APPLE SSD AP1024Z, 1 TB
  • Codex.app desktop, bundle 26.616.81150 (build 4306); its bundled binary reports codex-cli 0.142.0

Even on 0.142.0 — which already contains "Stop logging every Responses WebSocket event" (#29432) and "Filter noisy targets from persistent logs" (#29457) — the desktop codex app-server still floods ~/.codex/logs_2.sqlite:

  • 15 s sample: ~20.8 rows/s inserted into logs
  • Level mix of the most recent 500 rows: 493 TRACE / 7 DEBUG (~98.6% TRACE)
  • MAX(id) climbed 41,235 → 55,007 across a clean app restart while the retained row count stayed pinned at ~3,072 — i.e. the insert-and-prune churn described above
  • logs_2.sqlite-wal re-bloats to ~5 MB within seconds; PRAGMA wal_checkpoint(TRUNCATE) only clears it transiently while the app keeps writing

So on the macOS desktop path the 85% reduction doesn't appear to be enough yet — TRACE is still the overwhelming majority of persisted rows.

As an alternative to the tmpfs-symlink workaround, this stops the churn without losing useful logs (DEBUG/INFO/WARN still recorded):

CREATE TRIGGER codex_block_trace_logs
BEFORE INSERT ON logs
WHEN NEW.level = 'TRACE'
BEGIN
  SELECT RAISE(IGNORE);
END;

(RAISE(IGNORE) skips the row without consuming the AUTOINCREMENT counter.) After applying it the insert rate drops to ~0/s and the WAL stays at 0.

ChiTienHsieh · 26 days ago

I prepared a candidate fix, but this repository currently limits opening pull requests to collaborators, so I cannot open the draft PR directly from my fork.

Compare branch:

https://github.com/openai/codex/compare/main...ChiTienHsieh:codex/reduce-log-db-churn

Commit:

11722a0713 Reduce SQLite log churn

What it changes:

  • Changes the SQLite log persistence default filter from TRACE to INFO.
  • Keeps explicit OFF targets for log, codex_otel.log_only, and codex_otel.trace_safe.
  • Adds a sink-side guard to drop bridged tracing-log events whose final tracing target is log, because the outer target filter can see the original log target before bridging.
  • Continues dropping opentelemetry_sdk TRACE/DEBUG timer/meta events.
  • Adds focused tests for the default filter and sink-side dropped targets.

Validation:

  • git diff --check
  • cargo test -p codex-state filter_tests
  • Locally built a patched Codex binary on an Apple Silicon MacBook.
  • Ran a patched Codex session through tmux and verified that process had TRACE|log count 0 in logs_2.sqlite.
  • Resumed a Codex session through the patched binary and verified that resumed process had TRACE|log count 0.

Important caveat:

This is a Codex-generated fix and has not yet been reviewed by a human. The runtime smoke test was also performed by Codex on a macOS Apple Silicon machine. It appears to solve the reproduced local churn, but it needs maintainer/human review before being treated as correct upstream.

chensenxiang · 26 days ago

@ChiTienHsieh this matches what I'm seeing — the noise is essentially all TRACE|log, so flipping the persistence default from TRACE to INFO should address the bulk of it.

As an independent data point on the same machine (macOS / Apple Silicon, codex-cli 0.142.0 desktop app-server): instead of patching the binary I added a sink-level block at the SQLite layer —

CREATE TRIGGER codex_block_trace_logs
BEFORE INSERT ON logs
WHEN NEW.level = 'TRACE'
BEGIN
  SELECT RAISE(IGNORE);
END;

After this, the insert rate drops from ~20.8 rows/s to ~0/s and logs_2.sqlite-wal stays at 0, while DEBUG/INFO/WARN rows still persist — i.e. the same TRACE|log = 0 end state your patch reaches, just enforced from the DB side rather than the filter. So it's consistent that TRACE is the entire problem and an INFO default would fix it at the source. Hope this helps the review.

melroy89 · 25 days ago

Please fix ASAP. Ssd prices aren't fun anymore.

denispol · 25 days ago

The merged PRs (#29432/#29457/#29599) excluded noisy targets but left the root cause: the persisted sink is hardcoded to LevelFilter::TRACE (state/src/log_db.rs), independent of RUST_LOG. I've prepared a focused fix:

  • Configurable persisted level, default INFO (was TRACE); env CODEX_LOG_DB_LEVEL + [log] level.
  • Real off-switch: [log] persist = false / CODEX_LOG_DB=off.
  • Bounded WAL: journal_size_limit + wal_autocheckpoint (logs DB only), startup + periodic best-effort TRUNCATE checkpoint (was startup-only PASSIVE).

Diff/branch for reference: https://github.com/denispol/codex/pull/1 — happy to open an upstream PR if the team wants it.

nyavana · 25 days ago

The problem still exist for codex-cli v0.142.2 on WSL2

ccbibo · 25 days ago

I can reproduce this class of issue on macOS with a current Codex Desktop build, and I want to add that this has real SSD wear implications for normal users who would not notice the background write churn.

Environment:

  • Product: Codex Desktop
  • Codex Desktop: 26.623.30605, build 4415
  • CLI: codex-cli 0.142.2
  • OS: macOS 26.5.1, arm64
  • Process: /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled
  • Active DB: ~/.codex/logs_2.sqlite in WAL mode
  • Process environment included RUST_LOG=warn

What I observed before applying any workaround:

  • logs_2.sqlite: about 110 MB
  • logs_2.sqlite-wal: about 5.1 MB during observation
  • Three Codex processes had the SQLite DB/WAL open, with the active writer dominated by the Codex Desktop app-server process.
  • A 20-second file sample showed the WAL mtime updating roughly every 1-2 seconds even when the WAL size was stable.
  • A 60-second SQLite sample showed TRACE dominating recent rows. One sample showed approximately 1,582 TRACE rows from the app-server process in the last 60 seconds.
  • A 15-second delta sample showed 377 inserted row ids, with 363 TRACE rows, about 24 TRACE rows/sec.
  • Recent TRACE rows were mostly low-level websocket/tokio-tungstenite read/poll/frame parsing messages, including WebSocketStream.with_context, Stream.poll_next, Read.read, received frame, and similar internals.
  • The SQLite logs autoincrement sequence had reached about 19.7 million inserted rows.

Impact:

  • This is not just a disk-space issue. It is avoidable SSD write pressure during normal Codex Desktop usage.
  • Based on the observed WAL/page churn and inserted-row count, the accumulated host writes attributable to this logging path are plausibly in the tens to hundreds of GB range on this machine. The exact NAND write amplification cannot be measured from macOS here because SMART Data Units Written is not exposed, but the background write pattern is clearly abnormal.
  • A normal user would not discover this unless they inspect SQLite/WAL files or disk I/O manually, so the issue can silently continue accumulating writes.

Expected behavior:

  • Production Codex Desktop should not persist TRACE/DEBUG/INFO dependency and frame-level transport logs to a local SQLite database during ordinary use, especially when RUST_LOG=warn is present.
  • The persistent SQLite feedback log sink should respect an effective production threshold, use strict size/write caps, and avoid storing low-level protocol/frame logs or raw-ish payload details by default.
  • There should be a documented user-facing kill switch for persistent local feedback logs, not just analytics export settings.

Local mitigation I applied:

  • Set [analytics] enabled = false in ~/.codex/config.toml.
  • Added a local SQLite trigger to drop TRACE, DEBUG, and INFO inserts into logs while preserving WARN/ERROR.
  • Ran PRAGMA wal_checkpoint(TRUNCATE);.
  • After that, a fresh 30-second validation window showed delta_ids=0, and the WAL stayed at 0B with stable mtime.

Please treat this as a product bug, not just a diagnostic inconvenience. The app should not silently write high-frequency TRACE logs to user SSDs in production. Please clarify why this SQLite logging sink bypasses or differs from RUST_LOG=warn, and what release will change the default behavior to prevent unnecessary SSD wear.

ccbibo · 25 days ago

Follow-up: I am requesting that OpenAI route this issue to the appropriate support/customer-success path for compensation or account credit, not only engineering triage.

This bug silently caused avoidable SSD writes on user hardware during normal Codex Desktop usage. The user did not opt into production TRACE-level SQLite persistence, and the process environment showed RUST_LOG=warn. If the user had not manually inspected ~/.codex/logs_2.sqlite and the WAL behavior, the background writes would have continued.

I understand GitHub issues are primarily for engineering tracking, but please provide an official support route for affected paid users to request compensation/account credit for unnecessary hardware wear and time spent diagnosing/remediating this product defect. At minimum, OpenAI should:

  1. Publicly acknowledge whether this was expected or a product bug.
  2. Explain why RUST_LOG=warn did not prevent TRACE persistence into SQLite.
  3. State the release/build that fully fixes the issue.
  4. Provide a documented setting to disable persistent local feedback logs.
  5. Provide an official path for affected users to request subscription/account credit.

This should not be treated as a harmless log-size issue; it caused real background disk writes on customer machines.

crea7or · 24 days ago

Apart from sqlite logs there are much more to optimize and at least to define one folder for temporary files in codex config. Right now there are at least three solder in the system where Codex write data (these stats from just a few sessions like for day):

<img width="422" height="514" alt="Image" src="https://github.com/user-attachments/assets/8ebb9f26-a324-4684-b930-eaa1ed0e9b95" />

<img width="304" height="565" alt="Image" src="https://github.com/user-attachments/assets/5d0dedf6-2bb5-4d93-bd87-995f5cdfdd72" />

<img width="298" height="208" alt="Image" src="https://github.com/user-attachments/assets/da28aa4a-754b-49f7-b5a2-d3f5b87b886f" />

ChiTienHsieh · 24 days ago

@chensenxiang thanks — that lines up with my measurements too: TRACE persistence is the dominant churn source. The branch above moves that drop upstream into the SQLite sink (default TRACEINFO plus a sink-side guard) instead of relying on a local trigger, so INFO/WARN/ERROR still persist and full TRACE stays available via RUST_LOG=trace on the file-log path.

Tests pass locally:

$ cargo test -p codex-state sqlite_sink
running 2 tests
test log_db::filter_tests::sqlite_sink_drops_mirror_and_bridged_log_events ... ok
test log_db::filter_tests::sqlite_sink_default_filter_persists_info_and_drops_noisy_low_level_logs ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 146 filtered out
jiehai4 · 24 days ago

Has Codex Desktop been fixed? It's been so many days already. Earlier they said it would still write to the hard drive like crazy even in silent mode, so I don't even dare to open Codex.

grayaet · 23 days ago

I can confirm this issue on Windows Codex Desktop.

Environment:

  • Windows 10
  • Codex Desktop app version during the measurement: OpenAI.Codex_26.623.4041.0_x64__2p2nqsd0c76g0
  • Watched files:
  • C:\Users\<user>\.codex\logs_2.sqlite
  • C:\Users\<user>\.codex\logs_2.sqlite-wal
  • C:\Users\<user>\.codex\state_5.sqlite*

I ran a 5-hour metadata-only monitor from 2026-06-26 09:40:45 to 2026-06-26 14:40:56.

Results:

  • Codex.exe wrote about 13.4 GB during the observed period.
  • Average write rate while the process was visible: about 49 MB/min.
  • Peak observed minute: about 231 MB/min.
  • logs_2.sqlite file size did not grow:
  • start: 2555.461 MB
  • end: 2555.461 MB
  • logs_2.sqlite-wal barely changed:
  • start: 36.926 MB
  • end: 37.150 MB

However, SQLite metadata showed heavy churn:

  • logs.MAX(id) changed from 1039968250 to 1047999849
  • That is about 8,031,599 new row ids during the monitor window.
  • Actual row count only changed from 93492 to 99594, so this looks like insert/delete/rotation/page churn rather than normal file growth.

Top new log groups by row count:

  • TRACE codex_api::sse::responses: 7705 rows
  • TRACE codex_mcp::connection_manager: 1263 rows
  • TRACE log: 1110 rows
  • DEBUG codex_core::stream_events_utils: 352 rows

Largest group by estimated payload size:

  • TRACE codex_api::endpoint::responses_websocket: 75 rows, about 60 MB

This makes the issue hard to detect by only watching file size: the SQLite file can stay stable while the app still writes heavily to disk.

I have not applied the SQLite trigger workaround yet, but my telemetry suggests that dropping or reducing TRACE logging would likely reduce the write load significantly.

mtsitzer · 23 days ago

This needs to be a critical priority. People should not have to resort to workarounds in order to preserve their hardware when utilizing this product. I have not used the CLI/Desktop app (and therefore also am not using my rate limit) for several days after confirming that I am impacted by this issue. We should be getting partial refunds or a non-expiring limit reset as compensation for this.

darlingm contributor · 23 days ago

We're on day 4 of the last response here from OpenAI, when some mitigations were applied that had varying impact. I don't get it. I thought this would be a 5 alarm fire until completely fixed.

<img width="1448" height="1086" alt="Image" src="https://github.com/user-attachments/assets/6cd61a20-ceb7-46c7-a8a7-99e6d2ad4808" />

W944 · 23 days ago

Releasing Codex with this bug is akin to medical malpractice; like a surgeon forgetting a scalpel inside a patient.

taqyon · 22 days ago

The bug destroyed my Samsung 980 SSD, data lost into the void.

<img width="1280" height="1169" alt="Image" src="https://github.com/user-attachments/assets/37713094-10c4-48cd-a132-622305a68233" />

oorangee97 · 22 days ago

I can still reproduce high-volume persistent SQLite feedback logging on my machine after updating Codex.

Environment:

  • macOS
  • Codex desktop app version: 26.623.42026
  • App build: 4514
  • Managed Codex version shown by app-server: 0.142.0-alpha.6
  • Database path: ~/.codex/logs_2.sqlite

Observed before mitigation:

  • logs_2.sqlite size: about 441-459 MB
  • logs_2.sqlite-wal size: about 14 MB before checkpoint/truncate
  • total retained rows: about 68k
  • TRACE rows: about 57%
  • recent activity observed earlier: about 3,100 rows in 10 minutes, about 2,500 TRACE rows
  • largest retained estimated source observed: codex_client::transport TRACE, about 375 MB
  • latest log time before mitigation: 2026-06-28 22:32:48 local time

I did not inspect or share feedback_log_body values because they may contain sensitive diagnostic or conversation-related content.

Temporary mitigation applied:

  1. Quit Codex.
  2. Backed up ~/.codex/logs_2.sqlite, ~/.codex/logs_2.sqlite-wal, and ~/.codex/logs_2.sqlite-shm.
  3. Added this trigger:

CREATE TRIGGER IF NOT EXISTS block_log_inserts
BEFORE INSERT ON logs
BEGIN
SELECT RAISE(IGNORE);
END;

  1. Ran PRAGMA wal_checkpoint(TRUNCATE).

Validation after mitigation:

  • First sample:

row_count = 68719
max_id = 3604997
latest_log_time = 2026-06-28 22:32:48
logs_2.sqlite-wal = 12K

  • Second sample after 60 seconds:

row_count = 68719
max_id = 3604997
latest_log_time = 2026-06-28 22:32:48
logs_2.sqlite-wal = 12K

  • Additional sample after using Codex again:

row_count = 68719
max_id = 3604997
latest_log_time = 2026-06-28 22:32:48

This suggests the trigger successfully stopped inserts, but the underlying persistent logging issue still appeared present on my installed version before mitigation.

daryll-swer · 22 days ago
The bug destroyed my Samsung 980 SSD, data lost into the void.

I'm pretty sure OpenAI employees don't care about customers' SSDs at this point.

radfaraf · 22 days ago

Do what oorangee97 says 2 comments above the small DB change fixes it until we get a real fix. Codex did a great job of proving the issue was happening for me just by pointing it at this URL and asking for it to check if the issue is happening here.

matthew-walters · 22 days ago

OpenAI team,
What’s the process for receiving compensation for the verifiable damage done to my SSD ?

wilywork · 22 days ago

I made a small cross-platform workaround script for this issue.

It works on Linux, macOS and Windows.

Trigger used by default:

CREATE TRIGGER IF NOT EXISTS codex_block_trace_logs
BEFORE INSERT ON logs
WHEN UPPER(NEW.level) IN ('TRACE')
BEGIN
  SELECT RAISE(IGNORE);
END;

Requirement:

Bun already includes SQLite support through bun:sqlite, so you do not need to install sqlite3 separately if you run the script forcing Bun's built-in SQLite backend.

Usage, block TRACE log default:

bun codex-log-blocker-crossplatform.js

Check status:

bun codex-log-blocker-crossplatform.js --status

Block all logs:

bun codex-log-blocker-crossplatform.js --block-all

Unblock logs again:

bun codex-log-blocker-crossplatform.js --unblock

This is only a workaround until there is an official fix.

codex-log-blocker-crossplatform.js Updated

Thanks @oorangee97 for sharing the initial trigger workaround idea, and thanks @daryll-swer / @chensenxiang for pointing out that periodically rebuilding/compacting the database is not ideal here.

daryll-swer · 21 days ago

@wilywork

It cleans the Codex SQLite log database, runs VACUUM...

VACUUM does result in more writes on the SSD; if the DB file is small enough, it's probably best to delete the entries but leave file size as-is rather than to rewrite on the SSD with VACUUM.

chensenxiang · 21 days ago

@daryll-swer is right that VACUUM is counterproductive here — it rewrites the whole DB, so a periodic clean-and-VACUUM script trades the logging writes for VACUUM writes. It treats the symptom after the bytes have already hit the disk.

It's cheaper to stop the rows from ever being written. A BEFORE INSERT trigger that drops TRACE at the SQLite layer needs no cron, no cleanup, and no VACUUM:

CREATE TRIGGER codex_block_trace_logs
BEFORE INSERT ON logs
WHEN NEW.level = 'TRACE'
BEGIN
  SELECT RAISE(IGNORE);
END;

On macOS / Apple Silicon, codex-cli 0.142.0 desktop, this takes the insert rate from ~20.8 rows/s to ~0 and keeps logs_2.sqlite-wal at 0, while DEBUG/INFO/WARN still persist. RAISE(IGNORE) skips the row without consuming the AUTOINCREMENT counter, so there's nothing to clean up later. It survives until the next time Codex re-creates the DB (e.g. rotation to a new logs_N.sqlite), at which point you'd re-apply it — or, better, the upstream TRACEINFO default fix lands and none of this is needed.

jiehai4 · 21 days ago

Thanks everyone for the discussion, I took in all your suggestions and wrote two simple Python scripts. You can run them in PowerShell, and for the second one you need to wait 60 seconds to get the final result.

@'
import os, sqlite3

path = os.path.join(os.path.expanduser("~"), ".codex", "logs_2.sqlite")
con = sqlite3.connect(path, timeout=10)
con.execute("PRAGMA busy_timeout = 10000;")

con.executescript("""
CREATE TRIGGER IF NOT EXISTS codex_block_trace_logs
BEFORE INSERT ON logs
WHEN NEW.level = 'TRACE'
BEGIN
  SELECT RAISE(IGNORE);
END;

PRAGMA wal_checkpoint(TRUNCATE);
""")

con.close()
print("OK: TRACE log blocker installed")
'@ | python -
@'
import os, sqlite3, time

path = os.path.join(os.path.expanduser("~"), ".codex", "logs_2.sqlite")

def snap(label):
    con = sqlite3.connect(path, timeout=5)
    cur = con.cursor()
    print(label)
    print("trigger:", cur.execute("""
      SELECT name FROM sqlite_master
      WHERE type='trigger' AND name='codex_block_trace_logs'
    """).fetchall())
    print("levels:", cur.execute("""
      SELECT level, COUNT(*) FROM logs GROUP BY level ORDER BY COUNT(*) DESC
    """).fetchall())
    print("max:", cur.execute("SELECT COUNT(*), MAX(id), MAX(ts) FROM logs").fetchone())
    con.close()

snap("before")
time.sleep(60)
snap("after_60s")
'@ | python -

After testing this on Windows / Codex Desktop, the result looked good:
Trigger installed: codex_block_trace_logs
During a 60-second observation window:TRACE did not increase; it actually decreased from 23159 to 23138 due to old-row retention/rotation
INFO / DEBUG / WARN continued to be written normally
logs_2.sqlite-wal stayed around 4.16 MB and did not keep growing noticeably

So this does not disable all logging. It only blocks the noisiest TRACE level.

interfector18 · 21 days ago

Guys, if on linux use https://github.com/graysky2/anything-sync-daemon

To put the entire folder on tmpfs (ram), this will completely absorb the churn, given you also tackle the size growth.

daryll-swer · 21 days ago
Guys, if on linux use https://github.com/graysky2/anything-sync-daemon To put the entire folder on tmpfs (ram), this will completely absorb the churn, given you also tackle the size growth.

Oh, that's a good one. Can we replicate this on macOS 26 (Apple M chips)? @interfector18

wbdb · 21 days ago

Hasn't the error been fixed? (Windows Codex App)

W944 · 21 days ago

You don’t want the entire folder on ramdisk.
Just the 3 SQLite files.

Just symlink the 3 files to /dev/shm/ on Linux
And on macOS do the same symlink after making a ramdisk with diskutil

Its all built in, no need to install anything else.

rtpm · 21 days ago

Can't Fable fix it ?

rosewtfly · 21 days ago

still not fixed on desktop app(windows and macOS)

rx-bob · 20 days ago

Created a small macOS utility for the log guard and ramdisk fix https://github.com/bitbybob/codex-ssd-fix
Mainly for my different devices but perhaps it helps someone else ✌️
It will only symlink the 3 SQLite files to the ramdisk.

Note: For now you have to run it once per reboot but Im considering to add it to the startup directly.

daryll-swer · 20 days ago

All these workarounds/hacks are getting ridiculous. OpenAI should just patch the root cause and close this out permanently.

darlingm contributor · 20 days ago
All these workarounds/hacks are getting ridiculous. OpenAI should just patch the root cause and close this out permanently.

Honestly, my gut feeling is they're so overloaded that none of them are even watching this issue anymore and are aware this is still a problem. I don't know how else to explain it.

W944 · 20 days ago
Created a small macOS utility for the log guard and ramdisk fix https://github.com/bitbybob/codex-ssd-fix Mainly for my different devices but perhaps it helps someone else ✌️ It will only symlink the 3 SQLite files to the ramdisk. Note: For now you have to run it once per reboot but Im considering to add it to the startup directly.

Prime example of overengineering :p
Just put that in a .sh :

disk=$(hdiutil attach -nomount ram://2097152)
diskutil erasevolume HFS+ CodexLogsRAM "$disk"
for f in logs_2.sqlite logs_2.sqlite-wal logs_2.sqlite-shm; do
  mv ~/.codex/$f ~/.codex/$f.bak 2>/dev/null
  touch /Volumes/CodexLogsRAM/$f
  ln -s /Volumes/CodexLogsRAM/$f ~/.codex/$f
done
rx-bob · 20 days ago
> Created a small macOS utility for the log guard and ramdisk fix https://github.com/bitbybob/codex-ssd-fix Mainly for my different devices but perhaps it helps someone else ✌️ It will only symlink the 3 SQLite files to the ramdisk. > > Note: For now you have to run it once per reboot but Im considering to add it to the startup directly. Prime example of overengineering :p Just put that in a .sh : `` disk=$(hdiutil attach -nomount ram://2097152) diskutil erasevolume HFS+ CodexLogsRAM "$disk" for f in logs_2.sqlite logs_2.sqlite-wal logs_2.sqlite-shm; do mv ~/.codex/$f ~/.codex/$f.bak 2>/dev/null touch /Volumes/CodexLogsRAM/$f ln -s /Volumes/CodexLogsRAM/$f ~/.codex/$f done ``

Fair enough but this wont add the log guard + I wanted to have a simple CLI, so I can just toggle it whenever with easy to remember commands. As I said made it for me, just thought I share.

That said OpenAI will most likely just lessen the reliance on this sqlite dbs, but not get rid of them. Anyone with long running loops should seriously consider to ramdisk these anyways.

huaqiangli · 20 days ago

when 0.143.0 will be released? thanks

lock-down · 20 days ago

<img width="2546" height="960" alt="Image" src="https://github.com/user-attachments/assets/d14e43d1-030f-4c97-b3a5-43ac156db24b" />
Alreadt Fix it?

0xdevalias · 19 days ago
Alreadt Fix it?

Linking for easier review:

And then from earlier:

Update at Jun 23, 2026: the following 3 PRs are merged, it could avoid 85% logs(feedback from my codex), so let me close this issue. Thanks @jif-oai for the fix. Stop logging every Responses WebSocket event #29432 (released in 0.142.0) Filter noisy targets from persistent logs #29457 (released in 0.142.0) * Stop persisting bridged log events #29599 (will be released in 0.143.0)

I haven't looked deeper to see if #29599 was also backported to a v0.142.x version or not; or whether that is even relevant now that #30771 landed.

lock-down · 19 days ago
> Alreadt Fix it?  已经修复了吗? Linking for easier review:链接方便查阅: https://github.com/openai/codex/releases/tag/rust-v0.142.5 [[codex] Backport websocket trace fix to release/0.142 #30771](https://github.com/openai/codex/pull/30771) fix(core) Remove full text websocket trace #30757 And then from earlier:然后是前面提到的 : > Update at Jun 23, 2026: the following 3 PRs are merged, it could avoid 85% logs(feedback from my codex), so let me close this issue.2026 年 6 月 23 日更新:以下 3 个 PR 已合并,可以避免 85% 的日志(来自我的代码库的反馈),因此我将关闭此问题。 > Thanks @jif-oai for the fix.感谢 @jif-oai 的修复。 > > Stop logging every Responses WebSocket event #29432 (released in 0.142.0)  (发布于 0.142.0 版本) > Filter noisy targets from persistent logs #29457 (released in 0.142.0)  (发布于 0.142.0 版本) > Stop persisting bridged log events #29599 (will be released in 0.143.0)(将在 0.143.0 版本中发布) I haven't looked deeper to see if #29599 was also backported to a v0.142.x version or not; or whether that is even relevant now that #30771 landed.我没有深入研究 #29599 是否也被移植到某个版本;或者既然 #30771 已经发布,这是否还有意义。

I tested 0.142.5 and it was not fixed

0xdevalias · 19 days ago
I tested 0.142.5 and it was not fixed

@lock-down True.. :( Do you have any details / specifics on which parts are still an issue?

lock-down · 19 days ago
> I tested 0.142.5 and it was not fixed我测试了 0.142.5 版本,问题仍然存在。 @lock-down True.. :( Do you have any details / specifics on which parts are still an issue?确实如此……:( 您能否提供一些细节/具体信息,说明哪些部分仍然存在问题?

Refer to this https://github.com/openai/codex/issues/29532#issuecomment-4850466476

Follow-up from the same affected Mac after updating to a newer Desktop build.同一台受影响的 Mac 在更新到较新的桌面版本后出现了后续问题。 Environment:  环境: Platform: macOS 27.0 (26A5368g), arm64平台:macOS 27.0 ( 26A5368g ), arm64 Codex app: 26.623.81905, build 4598Codex 应用: 26.623.81905 ,版本 4598 Embedded CLI: codex-cli 0.142.5嵌入式命令行界面: codex-cli 0.142.5 PATH CLI: codex-cli 0.142.5PATH CLI: codex-cli 0.142.5 Active desktop app-server: /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled已启用桌面应用服务器: /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled App-server PID during check: 39970, started Wed Jul 1 12:33:06 2026应用服务器进程 ID(PID): 39970 ,启动时间: Wed Jul 1 12:33:06 2026 * Local SQLite mitigation triggers were still enabled: sample_log_inserts, trim_sampled_logs本地 SQLite 缓解触发器仍然启用: sample_log_insertstrim_sampled_logs Important note: this is a protected-state check, not a raw insert-rate measurement. I did not remove the local mitigation this time. On this machine those triggers retain at most about one row every 10 seconds and keep only the newest 500 retained rows.重要提示:这是受保护状态检查,并非原始插入率测量。这次我没有移除本地缓解措施。在这台机器上,这些触发器最多每 10 秒保留一行数据,并且只保留最新的 500 行。 Current retained SQLite state:当前保留的 SQLite 状态: `` PRAGMA quick_check: ok journal_mode: wal row_count: 500 trace_rows: 500 page_count: 10480 freelist_count: 10378 page_size: 4096 latest retained row: 2026-07-01 13:13:46 Asia/Shanghai ` 30-second protected sample:30 秒保护采样: ` before: count=500 max_id=148375 trace_rows=500 max_trace_id=148375 max_ts=1782882765 after: count=500 max_id=148378 trace_rows=500 max_trace_id=148378 max_ts=1782882796 ` Top retained targets after the sample:样本筛选后保留的主要目标: ` TRACE log 480 TRACE hyper_util::client::legacy::pool 9 TRACE hyper_util::client::legacy::client 7 TRACE codex_app_server::message_processor 3 TRACE rmcp::service 1 ` File sizes at the end of the check:检查结束时的文件大小: ` ~/.codex/logs_2.sqlite 42926080 bytes ~/.codex/logs_2.sqlite-wal 8157632 bytes ~/.codex/logs_2.sqlite-shm 32768 bytes ` Conclusion from this machine: 26.623.81905 / build 4598 / codex-cli 0.142.5 does not look fixed yet. Even with the local sampled-retention trigger active, retained rows are still 500/500 TRACE and max_trace_id continues to advance. The local mitigation is containing retained rows and disk growth here; it is not evidence that the upstream Desktop app-server stopped feeding TRACE events into the persistent SQLite sink.从这台机器得出的结论: 26.623.81905 / build 4598 / codex-cli 0.142.5 版本似乎尚未修复。即使启用了本地采样保留触发器,保留的行数仍然是 500/500 TRACE,并且 max_trace_id 持续增加。本地缓解措施控制了保留的行数和磁盘增长;但这并不能证明上游桌面应用服务器已停止向持久性 SQLite 存储体提供 TRACE 事件。 I am not pasting raw feedback_log_body values because they may include local paths, tool output, or conversation/session details.我不粘贴原始的 feedback_log_body` 值,因为它们可能包含本地路径、工具输出或对话/会话详细信息。

I tested 0.142.5 and it was not fixed+11111111111111

MoonstersWeb3 · 18 days ago

so is there an official or best work around, reading this thread just has 100 people all saying "do this, do that, this works, this won't work"... would be cool to just clearly know what band-aid to actually use...

W944 · 18 days ago
so is there an official or best work around, reading this thread just has 100 people all saying "do this, do that, this works, this won't work"... would be cool to just clearly know what band-aid to actually use...

Nothing official.
Apply the patch that you vibe more with; they both work.
If you think editing the sqlite triggers is best, do that.
If you think putting it in a ramdisk is best, do that.

I’d argue ramdisk is the safer ‘apply once and forget about it’ option as it ensures zero ssd writes even if codex updates and recreates the sqlite and it loses the sqlite hack.

MoonstersWeb3 · 18 days ago
> so is there an official or best work around, reading this thread just has 100 people all saying "do this, do that, this works, this won't work"... would be cool to just clearly know what band-aid to actually use... Nothing official. Apply the patch that you vibe more with; they both work. If you think editing the sqlite triggers is best, do that. If you think putting it in a ramdisk is best, do that. I’d argue ramdisk is the safer ‘apply once and forget about it’ option as it ensures zero ssd writes even if codex updates and recreates the sqlite and it loses the sqlite hack.

can you please quote the ramdisk fix for me so i make sure i'm applying the right one

darlingm contributor · 18 days ago

Unofficial Codex SQLite Workaround Summary

The consensus seems to be that everyone should apply the SQLite trigger in the next comment. It's the easiest workaround, blocking the noisy TRACE rows, while still allowing DEBUG, INFO, WARN, and ERROR rows. This reduces what gets written, but it does not completely fix the logging behavior.

The main downside is that it is not permanent if the database is replaced. Right now, Codex can rebuild logs_2.sqlite if it is corrupt, and this workaround would not be included in the fresh database. Future versions could also change behavior in a way that requires re-applying it.

So, advanced users who want extra SSD protection may want to also consider RAM-backed SQLite files in the third comment. That's not too difficult for macOS/Linux, but harder on Windows.

darlingm contributor · 18 days ago

Recommended for Everyone: SQLite Workaround

EDIT: I'm a user just like you. This is "recommended for everyone" because it seems to be the user consensus as a good idea.

This blocks only TRACE rows in Codex's SQLite log database. It keeps DEBUG, INFO, WARN, and ERROR rows.

If you have sqlite3 installed, you can run the commands below. Otherwise, give Codex a link to these comments and ask it to apply the SQLite workaround. It should check your actual logs_2.sqlite path, install the trigger if missing, and run the WAL checkpoint.

The SQL being applied is:

CREATE TRIGGER IF NOT EXISTS codex_block_trace_logs
BEFORE INSERT ON logs
WHEN UPPER(NEW.level) = 'TRACE'
BEGIN
  SELECT RAISE(IGNORE);
END;

PRAGMA wal_checkpoint(TRUNCATE);

macOS / Linux with sqlite3

sqlite3 "$HOME/.codex/logs_2.sqlite" <<'SQL'
CREATE TRIGGER IF NOT EXISTS codex_block_trace_logs
BEFORE INSERT ON logs
WHEN UPPER(NEW.level) = 'TRACE'
BEGIN
  SELECT RAISE(IGNORE);
END;

PRAGMA wal_checkpoint(TRUNCATE);
SQL

Windows PowerShell with sqlite3

@'
CREATE TRIGGER IF NOT EXISTS codex_block_trace_logs
BEFORE INSERT ON logs
WHEN UPPER(NEW.level) = 'TRACE'
BEGIN
  SELECT RAISE(IGNORE);
END;

PRAGMA wal_checkpoint(TRUNCATE);
'@ | sqlite3 "$env:USERPROFILE\.codex\logs_2.sqlite"

Notes:

  • PRAGMA wal_checkpoint(TRUNCATE) is included to shrink the current WAL after installing the trigger.
  • If the database is busy, wait a little and retry.
darlingm contributor · 18 days ago

Advanced Add-On: RAM-Backed SQLite Files

This is a strong optional add-on, but I don't recommend it for most users. It protects your SSD only while Codex is actually writing the log database in RAM. If a recovery path or future update creates a fresh logs_2.sqlite back on disk, the RAM setup would no longer be protecting you.

I would still apply the SQLite workaround first. RAM-backed files reduce SSD writes by moving the database into memory, while the SQLite workaround reduces how much gets written to the database in the first place.

Honestly, if the notes below are not enough for you to know how to do it confidently, you might be better off only running the easier SQLite workaround. You could always point Codex here and have it help you implement this. There are not universal ready-to-run instructions here because it depends on your OS and how you want the RAM-backed storage to start across reboots.

The idea is to move the hot SQLite files off the SSD and into memory:

logs_2.sqlite
logs_2.sqlite-wal
logs_2.sqlite-shm

Important caveats:

  • Quit Codex before moving/symlinking these files.
  • Existing Codex processes generally need a restart to open the new files.
  • Prefer backing up the existing logs_2.sqlite* files and creating fresh empty files in RAM. Moving a huge existing DB into RAM can consume a lot of memory, and using fresh files avoids needing to VACUUM it.
  • If the RAM disk/tmpfs disappears while Codex is running, expect problems.
  • You may need to redo the setup after reboot unless you automate it.
  • I would not move all of CODEX_HOME into memory, because your configuration, session files, etc would not persist across reboots.

General shape

  1. Quit Codex.
  2. Move/back up the existing ~/.codex/logs_2.sqlite* files.
  3. Create a RAM-backed location.
  4. Create fresh empty logs_2.sqlite, logs_2.sqlite-wal, and logs_2.sqlite-shm files there.
  5. Symlink those three paths back into ~/.codex.
  6. Start Codex.

Linux

Use a tmpfs-backed location such as /dev/shm.

Do not use /tmp unless you know your system mounts /tmp in memory.

macOS

Use a RAM disk, for example with hdiutil.

Example macOS sketch

This is not a polished installer, but one snippet shared in the thread was:

disk=$(hdiutil attach -nomount ram://2097152)
diskutil erasevolume HFS+ CodexLogsRAM "$disk"
for f in logs_2.sqlite logs_2.sqlite-wal logs_2.sqlite-shm; do
  mv ~/.codex/$f ~/.codex/$f.bak 2>/dev/null
  touch /Volumes/CodexLogsRAM/$f
  ln -s /Volumes/CodexLogsRAM/$f ~/.codex/$f
done

Windows

This is possible with third-party RAM disk software plus symlinks or junctions, but I would not recommend it as a general workaround. Use the SQLite workaround unless you already know how you want to manage a Windows RAM disk.

mdziczkowski · 18 days ago

I would recommend a different approach for fixing this issue:

  • Logging must be optional and enabled manually by user
  • Codex may not ignore set by user log settings
  • Unless there is requirment for more detailed collecting, all levels bellow WARN must be set manually by user instead as default
  • Do not log anything that is not essential
matthew-walters · 18 days ago

Excuse my ignorance but why is it logging at TRACE in the first place ?

That seems ridiculous

darlingm contributor · 18 days ago
I would recommend a different approach for fixing this issue: Logging must be optional and enabled manually by user Codex may not ignore set by user log settings Unless there is requirment for more detailed collecting, all levels bellow WARN must be set manually by user instead as default Do not log anything that is not essential

100%. Everything else talked about here are just workarounds since OpenAI hasn't addressed this since the commits they added that at least made it better.

ramses · 18 days ago
Excuse my ignorance but why is it logging at TRACE in the first place ? That seems ridiculous

There is no reason whatsoever to use TRACE for a production software release.

I have disabled INSERTing into the log table as other users have suggested. You should do the same.

adessuquinho · 16 days ago

When will be fixed?

Zhong-z · 16 days ago

At this rate, the notification emails from this thread are going to write as much to my SSD as Codex itself 😂

hxscoy · 14 days ago

I dug into this a bit further since it's still causing significant SQLite growth on my machine even on the latest stable release.

Confirmed: PR #29599 ("Stop persisting bridged log events", merged 2026-06-23) fixes the single largest remaining noise source (target == "log" bridged events), but it is not included in the current stable release rust-v0.142.5 (published 2026-07-01, 8 days after the PR merged).

Evidence:

  • rust-v0.142.5's codex-rs/state/src/log_db.rs only has the opentelemetry_sdk DEBUG/TRACE guard in on_event — the if metadata.target() == "log" { return; } guard from #29599 is absent:

https://github.com/openai/codex/blob/rust-v0.142.5/codex-rs/state/src/log_db.rs#L200-L216

  • The fix IS present in rust-v0.143.0-alpha.36 (published 2026-07-05):

https://github.com/openai/codex/blob/rust-v0.143.0-alpha.36/codex-rs/state/src/log_db.rs#L200-L207

On my machine, a single ~104s session produced 1518 rows in logs_2.sqlite: 1309 (86%) were TRACE, and 961 of those (73% of all TRACE rows) had target == "log" exactly — precisely the events #29599 is meant to suppress.

Looks like the 0.142.x release branch just didn't pick up this cherry-pick. Could a patch release (e.g. 0.142.6) backport #29599 (and any other log-volume fixes merged since) so people on the stable channel actually benefit? Right now the only way to get this specific fix is the alpha channel.

daryll-swer · 14 days ago
At this rate, the notification emails from this thread are going to write as much to my SSD as Codex itself 😂

At this rate, all of us are paying monthly subscription fees to OpenAI to kill our SSDs for fun.

matthew-walters · 14 days ago

Here’s the local change I made to stop the excessive SQLite log writes:

--- codex-rs/app-server/src/lib.rs
+++ codex-rs/app-server/src/lib.rs
@@
-    let log_db = state_db.clone().map(log_db::start);
-    let log_db_layer = log_db
-        .clone()
-        .map(|layer| layer.with_filter(log_db::default_filter()));
+    // Disable the SQLite tracing log sink because it can cause heavy sustained
+    // writes to the Codex log database.
+    let log_db: Option<log_db::LogDbLayer> = None;
@@
-        .with(log_db_layer)

--- codex-rs/tui/src/lib.rs
+++ codex-rs/tui/src/lib.rs
@@
-    let log_db = state_db.clone().map(log_db::start);
-    let log_db_layer = log_db
-        .clone()
-        .map(|layer| layer.with_filter(log_db::default_filter()));
+    // Disable the SQLite tracing log sink because it can cause heavy sustained
+    // writes to the Codex log database.
+    let log_db: Option<log_db::LogDbLayer> = None;
@@
-        .with(log_db_layer)

This disables the SQLite tracing log sink in both the app server and TUI startup paths. It stops Codex from continuously writing tracing logs into ~/.codex/logs_2.sqlite, while leaving the other logging, feedback, and OpenTelemetry layers intact.

bdmitriy727 · 13 days ago

Same problem

agentHits · 13 days ago

@jif-oai @anp-oai @pakrym-oai @rka-oai @bolinfest @etraut-openai @xl-openai @aibrahim-oai @cconger @sayan-oai @canvrno-oai @celia-oai @owenlin0 @richardopenai @tamird @charlesgong-openai @felixxia-oai @fcoury-oai @viyatb-oai @adaley-openai @apanasenko-oai @shijie-oai @iceweasel-oai @won-openai @guinness-oai @jameswt-oai @rhan-oai @wiltzius-openai @winston-openai @codex @ericning-o @stefanstokic-oai @abhinav-oai @alexsong-oai @dylan-hurd-oai @charliemarsh-oai @fjord-oai @mchen-oai @mzeng-openai @nornagon-openai @adrian-openai @fc-oai @marksteinbrick-oai @zanie-oai @ddr-oai @gpeal @rasmusrygaard @xli-oai

I want to connect a few related user-impact threads here because #28224 should not be closed as only a “large SQLite file” issue. This is a local resource-safety, privacy, and auditability problem.

The current mitigations reduce some hot paths, but the persistent SQLite log sink still appears to be deny-list based. In current main / 0.143.0-alpha, log_db::default_filter() still starts from LevelFilter::TRACE and then excludes selected targets. That is fragile for a production Desktop/CLI app. The safe default should be low-volume by construction: WARN+ globally, INFO only for intentionally compact Codex-owned audit events, and TRACE only behind an explicit short-lived diagnostic mode.

Stable users also need a clear answer. As of the latest stable I checked, rust-v0.142.5, important follow-up behavior from #29599 was not present in stable even though it exists in alpha/main. Please publish the exact stable CLI version and Desktop build that fully contain the fix, and keep this issue open until that build is verified by aggregate-only disk/SQLite measurements.

There is also a privacy angle: persistent TRACE logging can retain local paths, tool output, session details, and payload-like diagnostic content. Users should not need to inspect or post raw feedback_log_body values to prove the bug. The official verification path should use aggregate metadata only: row-id deltas, retained row counts, level/target counts, WAL size/mtime, process bytes written, and checkpoint behavior.

This also connects to the app-server/plugin usage-audit gap discussed in #31125 / #30918. Local persistence is currently too noisy for disk safety but still not structured enough for usage triage. A better design would persist compact audit records locally: thread_id, turn_id, model, tool/plugin, timestamps, token counts, cache counts, billing/limit category, request byte sizes, and result/error summaries — without storing high-volume TRACE bodies in SQLite.

Concrete asks:

  • Change the persistent SQLite sink default away from always-on TRACE.
  • Add a documented kill switch and level/filter control for local persistent logs.
  • Add hard DB/WAL byte budgets, write-rate protection, checkpoint/truncate behavior, rotation/quarantine, and cleanup/migration for already-bloated installs.
  • Add release-gate tests that bound local disk writes across Desktop/CLI on macOS, Windows, and Linux.
  • Publish a pinned official workaround until the fixed stable Desktop + CLI builds are available.
  • Provide an official aggregate-only verification recipe so users can confirm they are protected without exposing private logs.
  • Add a local per-thread/per-turn usage audit path for app-server/plugin sessions.

Related context:

  • #30669: Codex-triggered local verification can cause broader system I/O amplification, so performance validation should measure total local bytes read/written, not only Codex latency.
  • #31125 / #30918: app-server/plugin sessions can affect usage without enough local token accounting, which points to the need for compact structured audit records rather than persistent TRACE logs.

Please treat this as a product reliability issue, not a cosmetic logging issue. Users should not have to discover SSD wear through Activity Monitor, SMART counters, or SQLite forensics.

darlingm contributor · 13 days ago

@agentHits I've come to the conclusion that no one from OpenAI is monitoring our comments on this issue anymore. I don't know how to explain this otherwise. I'm sure they wouldn't be reading all this and ignoring it. The only thing I can think of us more of us need to be tweeting to OpenAI employees and replying to Tibo's "what should Codex do next" tweets with "fix issue 28224 and stop destroying consumer SSDs!" This is an easy fix, and the delay since the few commits issued a while ago have cost consumers a lot more SSD endurance. Codex estimated to me low single digit to low tens of millions of dollars of damage to consumer SSDs from when the bug first happened until shortly after this issue was posted. This is ridiculous.

I think they're severely overburdened by new issues and simply can't keep up, and that several major issues like this, ridiculous CPU/GPU usage do to animations that can't be turned off which is chewing up probably millions of dollars in electricity, etc, just go by largely unnoticed. They should be able to run Codex loops that analyze what is causing the most customer pain, and I don't know why they aren't running that.

I mean, I'm running the workaround so it's not affecting me anymore, but it's really irritating that this bug ate up so much of my SSD endurance and that OpenAI isn't offering anything in return, let alone giving it another moment's thought.

daryll-swer · 13 days ago

What makes any of you think OpenAI staff/leadership cares about our SSDs? There are no incentives for them to care.

zhuhaow · 12 days ago

The only way to get this fixed is to make it loud. We need to tie this directly to their corporate narrative: OpenAI and Anthropic keep telling the world that AI is going to replace human workers, yet they can't even use their "god-like" models to fix a basic, clearly identified bug in their own code.

Instead of all these useless benchmarks ranking which model is "better," we need a meter to track the disastrous bugs they fail to fix. Let’s see a real-world benchmark: how many fully identified bugs can these "frontier models" actually resolve? Currently, not too many. That number should be the true measure of their capability.

hxscoy · 12 days ago

Quick follow-up — 0.143.0 (published today) actually has #29599 in it now, and it works.

I upgraded and ran a session that ended up hitting 5 reconnect retries plus an HTTP 403. Out of 189 log rows total, only 2 were TRACE, and neither had target=="log".

0xdevalias · 12 days ago
Quick follow-up — 0.143.0 (published today) actually has #29599 in it now, and it works.

Reading through the release notes; particularly the individual commits (since the summaries at the top are quite often VERY lacking), I also saw some other follow up fixes mentioned.

I forget the exact ordering of them; but these are the tabs I opened based on it + the earlier fixes it referred to + etc:

>
> This restores the low-volume per-request signal proposed in bk-nvidia#3 without bringing back per-WebSocket-event TRACE logging.

sui-an · 12 days ago
@agentHits I've come to the conclusion that no one from OpenAI is monitoring our comments on this issue anymore. I don't know how to explain this otherwise. I'm sure they wouldn't be reading all this and ignoring it. The only thing I can think of us more of us need to be tweeting to OpenAI employees and replying to Tibo's "what should Codex do next" tweets with "fix issue 28224 and stop destroying consumer SSDs!" This is an easy fix, and the delay since the few commits issued a while ago have cost consumers a lot more SSD endurance. Codex estimated to me low single digit to low tens of millions of dollars of damage to consumer SSDs from when the bug first happened until shortly after this issue was posted. This is ridiculous. I think they're severely overburdened by new issues and simply can't keep up, and that several major issues like this, ridiculous CPU/GPU usage do to animations that can't be turned off which is chewing up probably millions of dollars in electricity, etc, just go by largely unnoticed. They should be able to run Codex loops that analyze what is causing the most customer pain, and I don't know why they aren't running that. I mean, I'm running the workaround so it's not affecting me anymore, but it's really irritating that this bug ate up so much of my SSD endurance and that OpenAI isn't offering anything in return, let alone giving it another moment's thought.

i have uninstalled it, not give up, but waiting the issue Completely resolved

JuneDrinleng · 10 days ago

So I'm wondering if the new CodeX desktop application released on July 10th, which merged the original ChatGPT and GPT Work, solved the problem of excessive log reading and writing to the SSD😰?

HGT158 · 10 days ago
So I'm wondering if the new CodeX desktop application released on July 10th, which merged the original ChatGPT and GPT Work, solved the problem of excessive log reading and writing to the SSD😰?

Actually, that’s not the case. After updating, I’ve noticed the new Codex app (or ChatGPT, as it’s called) is still writing to my SSD at dozens of MB per second. What can I say? 😅😅

W944 · 9 days ago

Found some more useless writes;
~/Library/Application Support/Codex/sentry/scope_v3.json < 2.51 GB/day but it's just 128kb - constantly overwritten/pruned
~/.codex/models_cache.json < 1.05 GB/day but it's only 270 KB - constantly overwritten/pruned

Opened another ticket to track it though as it's not SQLite per se. https://github.com/openai/codex/issues/32496

etraut-openai contributor · 8 days ago

Thanks for the bug reports. We think we've addressed the causes of the excessive log writes. I'm going to close this issue as fixed. If you are still seeing excessive writes, please open a new bug report and provide details.

rodalpho · 8 days ago

Which version has it fixed? Is it in the desktop app?

etraut-openai contributor · 8 days ago

Our series of fixes for this problem are in the latest shipping desktop app, CLI, and IDE extension.

darlingm contributor · 8 days ago
Our series of fixes for this problem are in the latest shipping desktop app, CLI, and IDE extension.

Not quite yet. 0.144.1 does not have #31789, #31790, #31791, and #31792. 0.145.0-alpha.4 includes them, so 0.145.0 likely will too.

Also, please see #32496. A different root cause is causing about 1.3 TB/year in unnecessary writes, according to the numbers in the issue. (Much less than was on this issue.) Codex is continually rewriting models_cache.json and scope_v3.json.

Would it be possible for OpenAI to add a regression test that ensures: (1) Codex during idle doesn't make more than a very minimal amount of I/O; and (2) Codex during usage doesn't absurdly increase its I/O? It might be best to split 2, into one version that fakes the model to test the harness in isolation, and one end-to-end test that makes sure new model/harness combinations don't come up with something that bumps it significantly. The latter could of course trip from random model behavior. If OpenAI logged and compared the values, they would not only see spikes but also worrisome trends in I/O usage that could be investigated.

While at it, testing CPU and GPU usage similarly would be a big help. There are several issues about crazy high usage, often tracked down to animations being unnecessarily repeatedly updated a crazy number of times a second.

gustavopch · 8 days ago

@etraut-openai Codex has been tracking this issue for me. I asked its opinion (GPT 5.6 Sol High), and it doesn't think this is fully solved:

---

I retested the SQLite logging behavior on macOS. The upstream fixes have substantially reduced the original churn, but the current desktop build still persists noisy TRACE/DEBUG activity at a much higher rate than when the local filter is enabled.

| Scenario | App/engine | Mitigation | Duration | MAX(id) change | Rate | Net retained rows |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| Earlier affected build (July 1) | codex-cli 0.142.5 | Off | 96.9s | +3,212 | 33.1 IDs/s | +57 |
| Current desktop build (July 12) | App 26.707.51957, bundled codex-cli 0.144.0-alpha.4 | Off | 41.45s | +305 | 7.36 IDs/s | +9 |
| Same current build | App 26.707.51957, bundled codex-cli 0.144.0-alpha.4 | On | 41.89s | +19 | 0.45 IDs/s | +1 |

The mitigation is a local SQLite trigger that rejects TRACE/DEBUG records and known noisy targets. It does not block INFO, WARN, or ERROR logging.

This shows two things:

  1. The upstream fixes improved the unmitigated rate substantially: 33.1 -> 7.36 IDs/second.
  2. On the current build, enabling the filter still reduces churn by about 16x: 7.36 -> 0.45 IDs/second.

During the current-build test without mitigation, 305 IDs were allocated while only 9 additional rows remained. That is consistent with continuing insert-and-prune activity.

The newly retained records were dominated by:

  • codex_app_server::outgoing_message / TRACE: 133
  • codex_api::sse::responses / TRACE: 35
  • codex_mcp::connection_manager / TRACE: 30
  • hyper_util TRACE/DEBUG: 67

No log bodies were read during these measurements.

Is this residual TRACE/DEBUG churn considered expected after the fixes? If not, is desktop app 26.707.51957 missing part of the fix, and which desktop or bundled CLI version should contain it?

0xdevalias · 8 days ago
Is this residual TRACE/DEBUG churn considered expected after the fixes? If not, is desktop app 26.707.51957 missing part of the fix, and which desktop or bundled CLI version should contain it?

@gustavopch It seems like you're still using 0.144.0-alpha.4 in those tests; whereas an earlier comment from @darlingm suggests that there are more fixes that will land in 0.145.0-alpha.4+, that sound like they should address some if not all of those:

Not quite yet. 0.144.1 does not have #31789, #31790, #31791, and #31792. 0.145.0-alpha.4 includes them, so 0.145.0 likely will too.
daryll-swer · 8 days ago
We should be getting partial refunds or a non-expiring limit reset as compensation for this. --@mtsitzer

So are we getting some refunds/limit resets or ANY compensation for killing our SSDs? For weeks or nah?

gustavopch · 8 days ago

@0xdevalias Ah, thanks for pointing it out. I just updated from 26.707.51957 to 26.707.61608 and the bundled Codex CLI is still 0.144.0-alpha.4. I guess we'll have to wait a few more days.

HGT158 · 21 hours ago

So has this issue been fixed in the latest version of the Codex desktop app?