Codex DesktopがローカルのSQLiteへ高頻度に診断ログを書き込む問題を、macOSの実機で確認した。今回の環境では、Codexの応答中にlogs_2.sqliteの採番が毎秒約28件進み、約15.5万行を保持するデータベースの採番値は10億を超えていた。
対策として、ログ本文や会話履歴へ触れず、logsテーブルへの新規挿入だけを無視する可逆的なSQLiteトリガーを適用した。その後、10分間の連続監視とCodex稼働中の再診断で、採番値と保持行数が増えないことを確認した。
診断と修正を別のPCでも再現できるよう、macOS・Linux・Windows対応のスクリプトとCodexスキルを公開している。
公開リポジトリ:
TL;DR
- Codex Desktopの
logs_2.sqliteとWALは、ファイルサイズが安定していても内部で挿入と削除を繰り返している可能性がある。 - 今回のmacOS環境では、修正前にログIDが毎秒約28件増えていた。
- 診断ではログ本文を読まず、採番値、保持行数、DB/WALのサイズと更新時刻だけを測定した。
-
logsへのINSERTを無視するトリガーを適用後、602秒・61サンプルで採番値の増加は0だった。 - 直近のCodex稼働中の20秒診断でも、採番値、行数、DB/WAL更新回数はすべて0だった。
- この方法は公式修正ではなく、ローカルの診断ログを停止する可逆的な回避策である。
/feedbackやサポート調査に使う診断データも保存されなくなる。
背景:問題は「DBが大きい」だけではない
OpenAIのCodexリポジトリでは、~/.codex/logs_2.sqliteとそのWALへの大量書き込みが報告されている。
代表的なIssueでは、21日間で約37 TBのSSD書き込みを観測し、単純換算で年間約640 TBになる可能性が示された。また、保持行数が約50万行である一方、AUTOINCREMENTの採番値が55億を超えていた。これは、古い行を削除しながら新しいログを大量に挿入する「挿入・削除の churn」が起きていたことを示す。
このIssueは2026年6月23日にクローズされ、Codex 0.142.0以降へ複数のログ削減が入ったと説明されている。しかし、その後もWindows版などで高頻度なTRACEログの永続化が再現したという報告が続いている。
- Codex SQLite feedback logs can write ~640 TB/year and rapidly consume SSD endurance
- Excessive TRACE logging causes logs_2.sqlite/WAL growth and SSD write pressure
- Windows Codex still persists high-frequency TRACE logs
重要なのは、現在のDBファイルサイズだけを見ても、累積書き込み量は判断できないことだ。SQLiteが古いログを削除して一定の行数を保っている場合、見かけの容量は横ばいでもSSDへの書き込みは継続する。
検証環境
| 項目 | 値 |
|---|---|
| OS | macOS 26.4.1 arm64 |
| Codexランタイム | codex-cli 0.146.0-alpha.3.1 |
| アプリ |
ChatGPT.app 26.721.41059 build 5848 |
| 対象DB | ${CODEX_HOME:-~/.codex}/logs_2.sqlite |
| SQLiteモード | WAL |
| 検証日 | 2026年7月26日 |
検証時のランタイムは、最初の修正がリリースされたとされる0.142.0より新しい。それでも、この環境ではCodexの応答中に採番値が進む状態を確認した。
これは「すべての新しいCodexで同じ問題が起きる」という意味ではない。ビルド、OS、使用する機能、起動時間によって結果は変わるため、各PCで実測する必要がある。
診断方法
診断ではfeedback_log_bodyを一切読み取らない。ログ本文には会話、ローカルパス、ツール出力、プロトコルのペイロードなどが含まれる可能性があるためだ。
代わりに、次の値だけを一定間隔で測定した。
-
sqlite_sequenceに記録されたlogsテーブルの採番値 - 現在保持されている行数
- DBとWALのファイルサイズ
- DBとWALの更新時刻
- ログレベル別の保持行数
- 対策トリガーの有無と定義
特に重要なのは採番値である。
SELECT seq
FROM sqlite_sequence
WHERE name = 'logs';
古い行が削除されても、AUTOINCREMENTの採番値は基本的に戻らない。そのため、短時間の差分を取れば、その間に成功したINSERTの件数を追跡できる。
修正前に確認した状態
最初の診断では、次の状態を確認した。
| 指標 | 観測値 |
|---|---|
| DBサイズ | 約1.66 GB |
| 保持行数 | 約15.8万行 |
TRACE行 |
約10.5万行 |
| 採番値 | 約10.5億 |
| Codex応答中の増加速度 | 約28 ID/秒 |
保持行数が約15.8万行なのに対し、採番値は約10.5億だった。単純比較でも、現在の保持行数を大きく上回る回数の挿入が過去に行われている。
ただし、この差だけからSSDの物理書き込み量や残り寿命を算出することはできない。SQLiteのWAL、チェックポイント、ページ再利用、ファイルシステム、SSD内部の書き込み増幅が介在するためだ。
採用した回避策
対策には、公式Issueでも紹介されているSQLiteトリガー方式を採用した。
CREATE TRIGGER block_log_inserts
BEFORE INSERT ON logs
BEGIN
SELECT RAISE(IGNORE);
END;
このトリガーは、logsテーブルへ新しい行を追加する直前に処理を中断し、挿入を無視する。state_5.sqlite、セッション、会話履歴、ゴールなどには触れない。
この方式を選んだ理由
- DBファイルの削除や
VACUUMを必要としない - 会話や状態管理用の別DBへ触れない
- トリガーを削除すれば元へ戻せる
- 採番値の増加が止まったかを明確に確認できる
- SQLiteの現在のスキーマと競合する場合は処理を拒否できる
代償
この方法はTRACEだけでなく、INFO、DEBUG、WARN、ERRORを含むlogsへの新規挿入をすべて止める。
したがって、次のデータも保存されなくなる可能性がある。
-
/feedbackで送信する診断情報 - OpenAIサポートが障害解析に利用するローカルログ
- 不具合発生直前の詳細なランタイム記録
問題調査やサポート依頼を行う予定がある場合は、先に復元する必要がある。
「トリガーを作れた」だけでは修正完了にしない
SQLiteへトリガーが存在しても、対象DBが違う、CodexがDBを再作成した、定義が競合している、といった可能性がある。
そこで、修正完了の条件を次の2点に限定した。
-
block_log_insertsの定義が期待値と完全に一致する - Codexが実際に応答している間も
sqlite_sequenceが増えない
修正後、602秒間に61回サンプリングした結果は次のとおりだった。
| 指標 | 結果 |
|---|---|
| 監視時間 | 602秒 |
| サンプル数 | 61 |
| 採番値の増加 | 0 |
| 保持行数の増加 | 0 |
| トリガー状態 | expected |
さらに、別のタイミングでCodexの20プロセスが稼働している状態を20秒間測定した。
Verdict: MITIGATED
Sequence: 1050678189 -> 1050678189
Sequence delta: 0
Insert IDs/sec: 0.0
Rows: 156887 -> 156887
DB mtime changes: 0
WAL mtime changes: 0
Trigger status: expected -> expected
この測定では、採番値、保持行数、DB/WALの更新時刻がすべて変化しなかった。
修正と復元を模擬DBで往復テストする
実機で書き込みが止まっただけでは、復元機能が正しいかは分からない。そこで、使い捨てのSQLite DBに40ミリ秒間隔でログを挿入し、修正前、修正後、復元後を比較した。
| フェーズ | 判定 | 1秒間の採番増加 | トリガー |
|---|---|---|---|
| 修正前 | ACTIVE_CHURN |
+23 |
なし |
| 修正適用 | MITIGATED |
0 |
期待どおり |
| 修正後 | MITIGATED |
0 |
期待どおり |
| 復元 | NO_CHURN_OBSERVED |
0 |
なし |
| 復元後 | ACTIVE_CHURN |
+23 |
なし |
この結果から、トリガー適用中だけ挿入が止まり、トリガーを削除すると再び挿入できることを確認できた。
公開スクリプトで診断する
Python 3.9以上があれば、追加パッケージなしで実行できる。
macOS・Linux
git clone https://github.com/Sunwood-ai-labs/diagnose-codex-sqlite-writes.git
cd diagnose-codex-sqlite-writes
python3 scripts/diagnose_codex_sqlite_writes.py \
--duration 60 \
--interval 5 \
--verbose
測定中に通常のCodexプロンプトを1回実行する。アイドル状態で書き込みがないだけでは、問題が発生していない証拠として弱い。
JSONで保存する場合は次のように実行する。
python3 scripts/diagnose_codex_sqlite_writes.py \
--duration 60 \
--interval 5 \
--json
Windows PowerShell
git clone https://github.com/Sunwood-ai-labs/diagnose-codex-sqlite-writes.git
Set-Location .\diagnose-codex-sqlite-writes
py -3 .\scripts\diagnose_codex_sqlite_writes.py `
--duration 60 `
--interval 5 `
--verbose
主な判定
| 判定 | 意味 |
|---|---|
ACTIVE_CHURN |
測定中にログIDが増えた |
CHURN_DESPITE_TRIGGER |
対策トリガーがあるのにIDが増えた |
MITIGATED |
対策トリガーがあり、IDが増えなかった |
NO_CHURN_OBSERVED |
IDは増えなかったが、アクティブな再測定が必要 |
INCONCLUSIVE_NO_CODEX_PROCESS |
Codexが見つからず判定不能 |
CONFLICTING_TRIGGER |
同名だが異なるトリガーが存在する |
SAFETY_BLOCKED |
修正・復元に必要な明示確認が不足している |
修正を適用する
最初にCodex Desktop、Codex CLI、IDE連携を終了する。次に、診断ログが保存されなくなることを理解したうえで実行する。
python3 scripts/diagnose_codex_sqlite_writes.py \
--action mitigate \
--confirm-diagnostic-logging-loss \
--duration 60 \
--interval 5 \
--verbose
スクリプトは次の安全条件を確認する。
- 対象が実際の
logs_2.sqliteである -
logsテーブルが存在する - 同名の異なるトリガーがない
- 診断ログ喪失への確認フラグがある
- Codexが起動中なら、ライブDB変更への追加同意がある
- 適用後のトリガー定義が期待値と一致する
Codexを終了できず、ライブDB変更のリスクを明示的に受け入れる場合に限り、--allow-running-codexを追加できる。
python3 scripts/diagnose_codex_sqlite_writes.py \
--action mitigate \
--confirm-diagnostic-logging-loss \
--allow-running-codex \
--duration 60 \
--interval 5 \
--verbose
通常は、Codexを終了してから適用する方が安全である。
元へ戻す
診断ログを再び保存する場合は、次のコマンドでトリガーだけを削除する。
python3 scripts/diagnose_codex_sqlite_writes.py \
--action restore \
--confirm-enable-diagnostic-logging \
--duration 5 \
--interval 1 \
--verbose
復元処理は、期待する定義と一致したblock_log_insertsだけを削除する。同名の異なるトリガーが存在する場合は、上書きも削除も行わない。
この検証で証明できたこと
- 対象Macでは、CodexのSQLite診断ログ採番が実際に進んでいた
-
block_log_inserts適用後、Codex稼働中も採番値が増えなかった - 10分間の監視でも、成功したログ挿入は観測されなかった
- 模擬DBでは、修正の適用と復元を往復できた
- 診断・修正・検証・復元を1本のスクリプトで再現できる
この検証では証明していないこと
- SSDへ実際に書き込まれた物理バイト数
- SSD内部の書き込み増幅率
- すでに消費したTBWや残り寿命
- すべてのCodexバージョンとOSで問題が発生すること
- トリガーがCodexの将来のDB再作成やスキーマ移行後も残ること
- この回避策がOpenAIの公式サポート対象であること
SMARTがVerifiedであっても、過去に書き込み寿命を消費していない証拠にはならない。一方、短いSQLite測定から「SSDが壊れる」と断定することもできない。観測できたのは、SQLite上の論理的な挿入とファイル更新である。
運用上の結論
この問題への対応で最も重要なのは、SNS上の年間TBW推定値をそのまま自分のPCへ当てはめることではない。
まず、自分の環境でsqlite_sequenceとWAL更新を測る。書き込みが再現した場合だけ、Codexの更新状況と診断ログの必要性を確認したうえで回避策を選ぶ。そして、トリガーを置いただけで終わらせず、Codexの実稼働中に採番値が止まったことを再測定する。
設定の存在ではなく、実際の書き込み停止を完了条件にする。
公開スクリプトは、その診断、修正、検証、復元を同じ手順で行うためのものだ。CodexやDBが更新された後は、再度60秒診断を実行するとよい。