# Stopping Codex SQLite Log Growth with a Trigger

Source: https://blog.ferstar.org/en/posts/codex-sqlite-log-trigger/





> I am not a native English speaker; this article was translated by AI.

Codex recently started storing local logs in `~/.codex/logs_2.sqlite`. On my machine that database had already grown past 1GB. The real problem was not the WAL file itself, but the log table: `TRACE`, `DEBUG`, and `INFO` rows kept going into SQLite, creating unnecessary disk usage and IO.

The public configuration surface is limited here. `RUST_LOG` can reduce verbosity, `log_dir` only controls the plaintext TUI log, and `history.max_bytes` only applies to `history.jsonl`. I could not find a public retention, max-size, or journal-mode option for `logs_2.sqlite`.

So I used SQLite itself as the stopgap.

### July 2026 update: the upstream issue is not fully fixed

Codex `0.142.0` shipped several changes that reduced a few high-volume log sources, including some `target=log`, WebSocket, and OpenTelemetry mirror events. Those changes only removed specific sources. They did not make the SQLite log sink honor `RUST_LOG`, and they did not add a usable log-level, sampling, or retention setting.

Fresh reproductions still exist on `0.144.5`: one log database grew by about 529MB in a week, while another reached 2.795GB and continued writing 151 rows in 10 seconds. The upstream issue [#31142](https://github.com/openai/codex/issues/31142) remains open. The trigger below is therefore still a useful temporary stopgap, not an obsolete workaround that is safe to remove.

### Block new log rows with one trigger

```bash
sqlite3 ~/.codex/logs_2.sqlite "CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;"
```

The trigger is intentionally blunt: whenever something tries to insert into the `logs` table, SQLite ignores that insert.

Verification is also simple:

```bash
sqlite3 ~/.codex/logs_2.sqlite "
SELECT count(*) FROM logs;
INSERT INTO logs(ts, ts_nanos, level, target, feedback_log_body, estimated_bytes)
VALUES(strftime('%s','now'), 0, 'INFO', 'trigger_test', 'should_not_exist', 1);
SELECT count(*) FROM logs;
SELECT count(*) FROM logs WHERE target='trigger_test';
"
```

If the row count stays the same and `trigger_test` is `0`, the trigger is working.

### Windows PowerShell version

On Windows, the path is usually:

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

I verified this on a remote Windows machine. The test insert did not change the row count:

```text
trigger: block_log_inserts
before: 76387
after: 76387
trigger_test_rows: 0
```

### Restore log writes

```bash
sqlite3 ~/.codex/logs_2.sqlite "DROP TRIGGER IF EXISTS block_log_inserts;"
```

PowerShell:

```powershell
$db = Join-Path $env:USERPROFILE ".codex\logs_2.sqlite"
sqlite3 $db "DROP TRIGGER IF EXISTS block_log_inserts;"
```

### Compact the old logs too

The trigger only blocks new rows. It does not shrink a database that has already grown. After quitting Codex, run a checkpoint and `VACUUM`:

```bash
sqlite3 ~/.codex/logs_2.sqlite "
PRAGMA wal_checkpoint(TRUNCATE);
DELETE FROM logs WHERE level IN ('TRACE','DEBUG');
DELETE FROM logs WHERE level = 'INFO' AND ts < strftime('%s','now','-3 days');
VACUUM;
"
```

If Codex is still running, SQLite may report `database is locked`. That is expected. Quit Codex and run it again.

### The limit of this trick

This does not fix Codex's logging system. It is just local damage control.

The upside is that it needs no Codex patch, no release wait, and no background cleanup daemon. The downside is that `logs_2.sqlite` will no longer contain new local logs, which makes local debugging weaker. When you need logs, drop the trigger, reproduce the issue, then add the trigger again.

Long term, Codex should expose log database retention or max-size settings. Until then, one SQLite trigger is enough.

