目的
Codex Desktop で、プロジェクトのディレクトリ名変更・移動後に左側の Thread 表示がおかしくなった場合の復旧メモ。結構詰まったので、諸々Codexにまとめさせた。今後、仕様が変わるかもなので参考程度に。
背景
ローカルの作業ディレクトリ配下にプロジェクトが増えてきたため、カテゴリ別にディレクトリ移動・リネームを行った。
まず、移動・リネームによって、Codex Desktop 側が保持している古い作業ディレクトリとの紐付けがずれた。その結果、左側の Project / Thread 表示で以下のような問題が起きた。
- 以前は左側に出ていた Project や Thread が消えた
-
Current working directory missingと表示される Thread が出た - 移動後のディレクトリを Project として開いても、過去 Thread が自然に紐付かない
- 今回移動していないはずの Project でも Thread 表示が極端に減ったように見えた
そこで、Codex 側で保持しているディレクトリ名・パスの紐付けを合わせるために、session JSONL 内の cwd や Codex DB 側の threads.cwd を移動後のパスへ修正した。
しかし、それだけではなかなか元の表示に戻らなかった。さらに、session JSONL を一括修正した副作用で、ファイルの mtime が同じ時刻に寄ってしまい、一部 Project では古い Thread が一斉に「1h前」のような同じ時刻で表示され、並び順も不自然になった。
つまり、今回の不整合は「ディレクトリ移動・リネームだけ」で起きたものではない。移動後の cwd のズレに加えて、そのズレを Codex 側で帳尻合わせしようとした作業により session JSONL の mtime が変わったことも、表示の乱れに影響していた可能性が高い。
最終的には、cwd のズレだけでなく、session JSONL の mtime や Codex Desktop 側の表示キャッシュ/再スキャン状態も関係していそうだとわかった。
今回の問題では、Thread データ自体は失われていなかったが、以下の整合性が崩れて左側の Project / Thread 表示が不自然になった。
- Codex DB 側の
cwd - Codex の Project 登録パス
- session JSONL のファイル更新時刻
mtime - Codex Desktop 側の表示キャッシュ / 再スキャン状態
次回同じような状態になったとき、同じデバッグを繰り返さないために残す。
Codex が見ていそうな主な場所
SQLite DB
~/.codex/state_5.sqlite
特に重要なテーブル/カラム:
threads.id
threads.cwd
threads.updated_at_ms
threads.rollout_path
threads.archived
threads.pinned
threads.cwd が古いディレクトリを指していると、移動後の Project に Thread が自然に出てこない可能性がある。
Global State
~/.codex/.codex-global-state.json
Codex Desktop 左側の Project 登録パスに関係している。ディレクトリ移動後、ここに古いパスが残っていると Project の表示や紐付けが壊れる可能性がある。
Session JSONL
~/.codex/sessions/**/*.jsonl
各 Thread の実体ログ。JSONL 内にも cwd が入っている。
重要な注意:
JSONL を書き換えると、ファイルの mtime が現在時刻になる。Codex Desktop は Thread の並び順や最近表示で、この mtime も見ている可能性が高い。
そのため、JSONL を一括修正した後に mtime を戻さないと、古い Thread が全部「1h前」など同じ時刻に見えたり、左側の並び順や表示件数が崩れることがある。
今回わかったこと
Thread データは基本的に消えていなかった
左側に出なくなった Thread も、DB / JSONL には残っていた。左側に表示されていないだけだった。
open codex://threads/<id> は復旧手段として不安定
Terminal で以下を実行しても、Codex ロゴの画面で止まって開かない Thread があった。
open 'codex://threads/<thread-id>'
一方で、開ける Thread もあった。復旧手段としては信頼しすぎない方がよい。
Pin -> Unpin は有効な再表示トリガー
ある Thread を pin すると左側に出る。そこで unpin すると、Project が左側に Import 済みの場合は、その Project 配下に残ることがある。
確認できた挙動:
- Project が左側に Import 済みなら、Pin -> Unpin で Thread が Project 配下に復活することがある
- Project が左側に Import されていない場合、Pin -> Unpin しても Project 自体は自然には出てこない
つまり、Project を先に Import してから、必要な Thread を Pin -> Unpin するのが現実的。
mtime を戻したら表示がかなり正常化した
JSONL の cwd 修正時に、session ファイルの mtime が一括で同じ時刻に変わった。
その結果、一部 Project で古い Thread が一斉に「1h前」扱いになり、表示順が壊れた。
state_5.sqlite の threads.updated_at_ms に合わせて JSONL の mtime を戻したところ、複数 Project の左側表示がかなり復活した。
推定:
- Codex Desktop は DB の
updated_at_msだけでなく session JSONL のmtimeも表示/再スキャン/キャッシュ更新に使っている可能性がある -
mtimeが不自然に同時刻化すると、Project/Thread の表示が乱れる -
mtimeを本来の更新日時に戻すと、表示インデックスが再評価されて整合性が回復することがある
安全な復旧手順
1. まずバックアップ
最低限、以下をバックアップする。
~/.codex/state_5.sqlite
~/.codex/.codex-global-state.json
~/.codex/sessions/**/*.jsonl
バックアップ先の例:
~/.codex/path-migration-backups/<timestamp>-pre-migration-files.tgz
~/.codex/path-migration-backups/<timestamp>-sqlite-global-state-fix/
~/.codex/path-migration-backups/<timestamp>-restore-session-mtimes/
2. DB の cwd を確認
例:
sqlite3 ~/.codex/state_5.sqlite \
"select cwd, count(*) from threads group by cwd order by count(*) desc;"
存在しない古いパスが残っていないか見る。
3. Project 登録パスを確認
python3 -m json.tool ~/.codex/.codex-global-state.json
左側に出したい Project のパスが、移動後の正しいパスになっているか見る。
4. JSONL 内の cwd を必要な分だけ修正
~/.codex/sessions/**/*.jsonl 内の cwd を古いパスから新しいパスへ変える。
注意:
この操作で JSONL の mtime が変わる。後で必ず mtime を DB の updated_at_ms に戻す。
5. SQLite の threads.cwd を修正
threads.cwd も古いパスから新しいパスへ変える。
重要:
updated_at_ms は触らない。Thread の本来の更新日時として保持する。
6. JSONL の mtime を DB の updated_at_ms に戻す
JSONL を編集した後は、threads.rollout_path と threads.updated_at_ms を使って、session ファイルの mtime を戻す。
考え方:
-
threads.rollout_pathが存在する - パスが
~/.codex/sessions/配下 -
threads.updated_at_msがある - JSONL の
mtimeが一括編集時刻になっている -
mtimeが DB のupdated_at_msより不自然に新しい
この条件に合うものだけ os.utime() で戻す。
確認用の例:
python3 - <<'PY'
import sqlite3, os, datetime
from pathlib import Path
db = Path.home() / '.codex/state_5.sqlite'
sessions = Path.home() / '.codex/sessions'
conn = sqlite3.connect(db)
rows = conn.execute("""
select id, cwd, updated_at_ms, rollout_path
from threads
where rollout_path is not null and updated_at_ms is not null
""").fetchall()
conn.close()
large = []
for tid, cwd, updated_ms, rollout_path in rows:
p = Path(rollout_path)
if not p.exists():
continue
try:
p.relative_to(sessions)
except ValueError:
continue
mtime = p.stat().st_mtime
db_time = updated_ms / 1000
if mtime - db_time > 3600:
large.append((mtime - db_time, tid, cwd, db_time, mtime, str(p)))
print('remaining_large_diff_over_1h', len(large))
for diff, tid, cwd, db_time, mtime, path in sorted(large, reverse=True)[:20]:
print(
tid,
'diff_h=', round(diff / 3600, 1),
'db=', datetime.datetime.fromtimestamp(db_time).strftime('%Y-%m-%d %H:%M:%S'),
'mtime=', datetime.datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S'),
cwd,
)
PY
remaining_large_diff_over_1h 0 なら、少なくとも不自然に新しい mtime は残っていない。
復旧後の確認
Codex Desktop で以下を見る。
- 左側の Project が正しいパスで出ているか
- Project を開いたとき、その Project の過去 Thread が自然に出るか
- 古い Thread が全部同じ「1h前」などになっていないか
- 大きい Project で Thread が極端に減っていないか
必要なら Codex Desktop を再起動する。
まだ左側に出ない Thread がある場合:
- 対象 Project を左側に Import する
- 対象 Thread を Pin する
- 左側に出たら Unpin する
- Project 配下に残るか確認する
次回の重要な教訓
ディレクトリ移動後に Codex Thread の cwd を直す場合は、以下をセットでやる。
-
~/.codex/sessions/**/*.jsonlのcwdを直す -
state_5.sqliteのthreads.cwdを直す -
.codex-global-state.jsonの Project パスを直す - JSONL の
mtimeをthreads.updated_at_msに戻す
特に 4 が重要。
JSONL の中身だけ正しくても、mtime が一括更新時刻のままだと Codex Desktop の左側表示・並び順・最近表示が壊れる可能性がある。