前回の記事では、macOS上のCodex Desktopがlogs_2.sqliteへ診断ログを継続的に書き込む状態を実測し、可逆的なSQLiteトリガーで新規挿入を止めた。
今回はその続編として、Windows 11の別PCでも同じ現象が起きているのかを確認した。
結論から言うと、Codex Desktop 26.721.4979.0でも診断ログの継続的な挿入・削除を確認した。対策前は45秒でログIDが836進み、約18.6 ID/秒だった。対策後はCodexを実際に使いながら10分間監視し、新規ログIDの増加は0だった。
ただし、今回の10分監視では重要な補足も見つかった。
新規INSERTが止まっていても、既存ログの整理によって行数が減り、DBやWALが低頻度で更新されることがある。
「WALの更新時刻が一度でも変わったら対策失敗」と判定すると、正常に効いている回避策を誤って失敗扱いしてしまう。
TL;DR
- Windows 11 / Codex Desktop
26.721.4979.0でもSQLite診断ログのchurnを確認した - 対策前の45秒で最大ログIDは
+836、約18.6 ID/秒だった - DBは約758.4 MiB、WALは約26.1 MiBだった
- 保持行数が減っていても最大IDは増えており、挿入と削除が同時に進んでいた
-
block_log_inserts適用後、600秒・120サンプルで最大IDの増加は0だった - その間に既存ログが1,955行減り、WALのmtimeは4回変化した
- 主判定は「トリガーが残っている」「
sqlite_sequence差分が0」の2点 - Windowsの一般的なプロセスI/O値は、SSDへの物理書き込み量として扱えない
- 回避策は非公式であり、
/feedbackやサポート用の診断情報が保存されなくなる
診断スクリプトと匿名化した測定データは次のリポジトリへ追加した。
なぜWindowsでも調べたのか
元になったIssue #28224では、21日間で約37 TBのSSD書き込みが観測されたと報告されている。
その後、Codex 0.142.0以降へログ量を減らす修正が入り、報告者の環境では約85%削減されたとしてIssueはクローズされた。しかし「診断ログを完全に無効化する修正」ではない。
後続のIssue #31478では、修正後のバージョンでも約27.6 ID/秒で採番が進む状態が報告されている。そこで、現在使っているWindows PCを推測ではなく実測することにした。
検証環境
| 項目 | 値 |
|---|---|
| OS | Windows 11 Pro 64-bit、build 26200 |
| Codex Desktop | OpenAI.Codex 26.721.4979.0 |
| Python | 3.12.10 |
| 対象DB | %USERPROFILE%\.codex\logs_2.sqlite |
| SQLiteモード | WAL |
| DBサイズ | 795,271,168 bytes(約758.4 MiB) |
| WALサイズ | 27,373,312 bytes(約26.1 MiB) |
| 検証日 | 2026年7月26日 |
Windowsのストレージ管理画面では、CドライブのSSDはHealthy / OKだった。ただし、管理者権限なしではSMARTの累積書き込み量や消耗率を取得できなかった。
したがって、この記事で確認するのはSQLite上の論理的な挿入とファイル更新であり、SSDの残り寿命そのものではない。
同名DBが2個あった
最初に.codex配下の大きなファイルを調べると、logs_2.sqliteが2か所に存在した。
| パス | サイズ | 状態 |
|---|---|---|
%USERPROFILE%\.codex\logs_2.sqlite |
約758 MiB | 現在更新中 |
%USERPROFILE%\.codex\sqlite\logs_2.sqlite |
約392 MiB | DB本体とWALは更新なし |
単に同名ファイルを見つけて対策するだけでは、古いDBへトリガーを作ってしまう可能性がある。
診断ツールでは、次の優先順位でアクティブな保存先を解決する。
-
--dbで明示したパス --sqlite-homeCODEX_SQLITE_HOME-
config.tomlのsqlite_home ${CODEX_HOME:-~/.codex}/logs_2.sqlite
さらに、実際の更新時刻とCodexプロセスの稼働を照合した。
対策前の45秒監視
Codex Desktopを起動し、通常のやり取りを行いながら5秒間隔で測定した。
| 指標 | 開始 | 終了 | 差分 |
|---|---|---|---|
| 最大ログID | 134,976,616 | 134,977,452 | +836 |
| 保持行数 | 107,167 | 106,978 | -189 |
| 採番速度 | 約18.6 ID/秒 |
ここで重要なのは、保持行数は189件減ったのに、最大IDは836進んだことだ。
現在の行数だけを見ると「ログが増えていない」ように見える。しかし内部では新しいログを挿入し、古いログを削除している。WALはサンプル中ほぼ毎回更新され、DB本体もチェックポイントとみられる更新を繰り返していた。
保持ログ本文の生値は取得・公開せず、レベルとターゲットだけを集計した。
| レベル | 保持行数 |
|---|---|
TRACE |
64,107 |
DEBUG |
20,286 |
INFO |
19,784 |
WARN |
2,689 |
ERROR |
88 |
最も多かった組み合わせはcodex_api::sse::responsesのTRACEで、43,791行だった。
WindowsのプロセスI/O値には注意する
対策前、WindowsのWin32_PerfFormattedData_PerfProc_ProcessでCodex app-serverを測ると、IOWriteBytesPersecは20秒平均約1.59 MiB/s、最大約21.6 MiB/sだった。
一見すると、これを24時間や年間へ換算したくなる。
しかし、対策後も同じカウンターは15秒平均約1.79 MiB/sを示した。一方で、SQLiteの最大IDは固定され、WAL更新も大幅に減っていた。
WindowsのプロセスI/Oカウンターにはファイル以外のパイプなども含まれる。そのため、値をそのままSSDへの物理書き込み量として扱うことはできない。
短時間のプロセスI/O値から「年間○TB書く」「SSD寿命が○年縮む」と外挿するのは避けるべきだ。
可逆的な回避策を適用する
今回も、logsテーブルへの新規挿入だけを無視するSQLiteトリガーを使用した。
CREATE TRIGGER block_log_inserts
BEFORE INSERT ON logs
BEGIN
SELECT RAISE(IGNORE);
END;
このトリガーは、state_5.sqlite、会話履歴、セッション、ゴールには触れない。削除すれば診断ログを再開できる。
安全確認を含むスクリプトでは、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 `
--action mitigate `
--confirm-diagnostic-logging-loss `
--duration 60 `
--interval 5 `
--verbose
通常は、Codex Desktop、Codex CLI、IDEのCodex連携を終了してから実行する。
今回の実験はCodexとの対話セッション自体を監視対象にしたため、ライブDBへSQLiteのbusy_timeoutを設定したうえで適用した。スクリプトで同じことを行うには、ロック競合のリスクを明示的に受け入れた場合だけ--allow-running-codexを追加できる。
py -3 .\scripts\diagnose_codex_sqlite_writes.py `
--action mitigate `
--confirm-diagnostic-logging-loss `
--allow-running-codex `
--duration 60 `
--interval 5 `
--verbose
可能なら、Codexを終了してから適用する方が安全である。
適用直後の35秒確認
トリガー作成直後、5秒間隔で7回確認した。
| 指標 | 結果 |
|---|---|
| 最大ログID | 134,988,790 → 134,988,790 |
| 保持行数 | 107,154 → 107,154 |
| WAL mtime変化 | 0 |
| トリガー状態 | expected |
この短時間確認では、採番値、保持行数、WAL更新時刻がすべて固定された。
ただし、35秒だけで「今後もずっと止まる」とは言い切れない。そこで、同じCodexセッションを使い続けながら10分間監視した。
10分監視で見えたもの
5秒間隔で600秒、合計120回サンプリングした。
| 指標 | 結果 |
|---|---|
| 監視時間 | 600秒 |
| サンプル数 | 120 |
| 最大ログID | 134,988,790 → 134,988,790 |
| 採番値の増加 | 0 |
| 保持行数 | 107,154 → 105,199(-1,955) |
| DB mtime変化 | 1回 |
| WAL mtime変化 | 4回 |
| トリガー不在 | 0回 |
| 読み取りエラー | 0回 |
最大ログIDは10分間一度も増えなかった。つまり、logsテーブルへの成功した新規挿入は観測されていない。
一方、保持行数は途中で1,955行減り、DBのmtimeが1回、WALのmtimeが4回変化した。これは、すでに保存されていたログのretention cleanupやSQLiteのチェックポイントによるものと解釈できる。最後の約59秒ではWAL更新もなかった。
最初に作った簡易監視では「WALのmtime変化が1回でもあればCHECK_REQUIRED」という厳しすぎる条件にしていた。そのため、出力上は要確認になった。
しかし、次の事実を分けて考える必要がある。
- 新しい診断ログの挿入:
sqlite_sequenceが進む - 既存ログの整理:行数が減り、DB/WALが更新されることがある
- SQLiteメンテナンス:採番値が固定でもファイル更新が起きることがある
したがって、回避策の主判定は次の2点にする。
- 監視終了時にも
block_log_insertsが期待どおり存在する - Codex稼働中の
sqlite_sequence差分が0である
行数減少や低頻度のmtime変化は、主判定とは分けて報告する。
自分のPCで10分確認する
公開スクリプトでは次のコマンドで同じ条件を再現できる。
py -3 .\scripts\diagnose_codex_sqlite_writes.py `
--duration 600 `
--interval 5 `
--verbose
監視中にCodexへ通常のプロンプトを送り、実際のログ発生経路を動かす。
期待する出力は次のようなものだ。
Verdict: MITIGATED
Sequence: 134988790 -> 134988790 (+0, 0.0 ids/s)
Trigger block_log_inserts status: expected -> expected
WALのmtime変化が少数残っても、採番差分が0なら新規ログ挿入は止まっている。行数が減った場合は既存ログ整理として別に確認する。
匿名化した今回の測定値もJSONで公開している。
診断ログを元へ戻す
OpenAIサポートへ問い合わせる場合や、/feedback用の診断情報を再び保存したい場合は、トリガーを削除する。
py -3 .\scripts\diagnose_codex_sqlite_writes.py `
--action restore `
--confirm-enable-diagnostic-logging `
--duration 5 `
--interval 1 `
--verbose
復元するとSQLiteへの診断ログ書き込みも再開する。
今回証明できたこと
- Windows 11の対象PCでもSQLite診断ログの継続的な採番を確認した
- 保持行数が減っていても、新規挿入が並行していた
- トリガー適用後、Codex稼働中の10分監視で採番値は増えなかった
- 既存ログ整理による行数減少と低頻度のWAL更新は残り得る
- 採番値とmtimeを分けて判定する必要がある
- 同名DBが複数ある場合、アクティブな保存先の解決が重要である
今回証明していないこと
- SSDへ実際に書き込まれた物理バイト数
- SSD内部の書き込み増幅率
- すでに消費したTBW
- SSDの残り寿命
- すべてのCodexバージョン・機能・OSで同じ頻度になること
- 将来のCodex更新やDB再作成後もトリガーが残ること
- この回避策がOpenAI公式サポート対象であること
Windows上でSSDがHealthyと表示されても、過去に書き込み寿命を消費していない証拠にはならない。一方、短時間のSQLite測定だけでSSD故障を断定することもできない。
観測できた事実と、測定できていないSSD内部の状態は分けて扱う必要がある。
まとめ
前編のmacOSに続き、Windowsの新しいCodex DesktopでもSQLite診断ログのchurnを確認した。
今回のWindows環境では、対策前に約18.6 ID/秒で採番が進んでいた。block_log_inserts適用後は、Codexを使いながら10分監視しても採番値の増加は0だった。
そして、長めに監視したことで「新規挿入が止まっていても、既存ログ整理によるWAL更新は残る」という点も確認できた。
ファイルサイズやmtimeだけでなく、成功したINSERTを示す採番値を測る。
対策を置いただけで終わらせず、Codexの実稼働中にsqlite_sequenceが止まったことまで確認するのが重要だ。