CodexをWSLからWindowsへ移行し、消えたサイドバー履歴を復元する¶
復元できた範囲と重要な制約
WSLからWindowsネイティブ版Codexへ切り替えた検証環境で、通常セッションとアーカイブを合わせて1,000件超の索引を復元し、サイドバーから再び開ける状態まで戻した。
- WSL、旧Windows CLI、現Windows版の保存領域を別々にバックアップする
state_*.sqlite、認証情報、設定は混ぜず、rollout-*.jsonlだけを移す- スレッドIDを直接指定し、移行したJSONLを1件だけ検証する
- Codexの
app-serverAPIで通常スレッドとアーカイブの索引を再構築する
この手順はCodex CLI 0.144.4でコマンドとJSON Schemaを再確認した。 app-serverは実験的インターフェースであるため、別バージョンでは必ず同梱Schemaを生成してから使う。
Codexのサイドバーから過去のスレッドが消えても、会話本文が削除されたとは限らない。WSLとWindowsが別のCODEX_HOMEを参照し、Windows側の状態DBに旧セッションの索引がないだけの場合がある。
この記事は、実際に分散したセッションを統合し、通常スレッドとアーカイブのサイドバー索引まで復元した作業記録を、公開用のプレースホルダーへ置き換えたものだ。個人名、ユーザー名、スレッドID、プロジェクト名、保存ドライブ構成は含めていない。

履歴消失の多くは保存領域と索引の分離で起きる¶
Codexはローカル状態をCODEX_HOME配下に保存する。公式のConfiguration Referenceは、ここに設定、認証、ログ、セッションなどが含まれると説明している。1
Windows版へ切り替えた環境では、次の3領域に別々のセッションが残ることがある。
| 保存領域 | パスの例 |
|---|---|
| WSL側 | /home/<WSL_USER>/.codex |
| 旧Windows CLI側 | C:\Users\<WINDOWS_USER>\.codex |
| 現在のWindows版 | C:\Users\<WINDOWS_USER>\AppData\Local\OpenAI\CodexAppHome |
Windows版の保存先を固定値で決めつけてはいけない。 公式のWindows向け資料は%USERPROFILE%\.codexを案内しているが、 アプリの配布形態や移行状態によって別の場所が使われる環境もある。 実際の保存先は後述するcodex doctorで確認する。2
会話実体と一覧索引は別の層にある。
| 層 | 主な内容 | 移行時の扱い |
|---|---|---|
rollout-*.jsonl | 会話、ツール実行、セッションメタデータ | バックアップ後に無上書きで統合 |
state_*.sqlite | スレッド一覧などの状態DB | OS間でコピーしない |
auth.jsonまたはキーチェーン | 認証情報 | 移行対象にしない |
config.toml | ローカル設定 | 環境ごとに管理 |
.codex-global-state.json | アプリUIの状態 | 移行対象にしない |
OpenAIのヘルプも、ローカルで開始したチャットはそのコンピューターに残り、Codexの履歴はChatGPTの通常履歴と分かれていると説明している。3
最初にCodexと3つの保存領域を停止・特定する¶
バックアップと索引処理の前に、Codex/ChatGPTデスクトップアプリとCodex CLIを終了する。PowerShellでは関連プロセスを確認できる。
Get-Process -Name Codex,ChatGPT -ErrorAction SilentlyContinue |
Select-Object ProcessName, Id, StartTime
次に、Windowsネイティブのcodexが実際に使っている状態を診断する。
codex --version
codex doctor --summary --no-color --ascii
機械可読な診断結果が必要なら、マスク済みJSONをローカルだけで確認する。
$doctor = codex doctor --json | ConvertFrom-Json
$doctor.checks | Format-List
doctorのJSON構造はバージョンで変わり得る。フィールド名を決め打ちした移行スクリプトを先に作らず、現在の出力からCODEX_HOME、状態DB、rolloutとDBの差分を確認する。
診断結果もそのまま公開しない
codex doctor --jsonは診断情報をマスクするが、ローカルパスやサンプル値が含まれる可能性がある。Issueやブログへ貼る前に、ユーザー名、プロジェクト名、スレッドIDを再確認する。
バックアップはOSと世代を混ぜずに取得する¶
WSL、旧Windows CLI、現Windows版の保存領域を、それぞれ別のフォルダーへ丸ごと退避する。バックアップ先は元データと別のドライブが望ましい。
$stamp = Get-Date -Format "yyyyMMdd-HHmmss"
$BackupRoot = "E:\Codex-Migration-Backups\$stamp"
New-Item -ItemType Directory -Path $BackupRoot -Force | Out-Null
robocopy `
$WslCodexHome `
(Join-Path $BackupRoot "wsl-native\.codex") `
/E /COPY:DAT /DCOPY:DAT /R:1 /W:1
if ($LASTEXITCODE -ge 8) {
throw "WSL側バックアップに失敗: robocopy exit code $LASTEXITCODE"
}
robocopy `
$WindowsCodexHome `
(Join-Path $BackupRoot "windows-current\CodexHome") `
/E /COPY:DAT /DCOPY:DAT /R:1 /W:1
if ($LASTEXITCODE -ge 8) {
throw "Windows側バックアップに失敗: robocopy exit code $LASTEXITCODE"
}
robocopyは終了コード0〜7を成功または差分ありとして使う。 8以上を失敗として扱う。 旧Windows CLI側にも独自セッションがある場合は、3つ目のフォルダーへ分けて保存する。
障害復旧中はWSLとWindowsでSQLiteを共有しない¶
公式のWindows向け資料は、WSL側のCODEX_HOMEをWindows側へ向け、 設定、認証、セッションを共有する方法も案内している。 正常に動く構成で、この方法自体を否定するものではない。2
一方、公式リポジトリのIssue #23251では、 WSL CLIからWindows版のCODEX_HOMEを開いた際に、 SQLiteのマイグレーション不整合で起動できない事例が報告されている。 コピーしたDBのPRAGMA integrity_checkはokでも、 ランタイム間のマイグレーションチェックに失敗した。4
この記事の検証環境でも、復旧作業では次を分離した。
- Windows版はWindows側の
CODEX_HOMEと状態DBを使う - WSL版はWSL側の
CODEX_HOMEと状態DBを使う - OS間で移すのはバックアップ済みの
rollout-*.jsonlだけ - SQLiteへ
INSERT、UPDATE、スキーマ変更を行わない
これは通常運用の一般ルールではなく、履歴復旧時に既存状態を壊さないための安全境界である。
セッション実体はスレッドIDで無上書き統合する¶
移行前に各保存領域のJSONL件数を数える。
foreach ($home in @(
$WslCodexHome,
$LegacyWindowsCodexHome,
$WindowsCodexHome
)) {
$active = @(
Get-ChildItem (Join-Path $home "sessions") `
-Recurse -Filter "rollout-*.jsonl" `
-ErrorAction SilentlyContinue
).Count
$archived = @(
Get-ChildItem (Join-Path $home "archived_sessions") `
-Recurse -Filter "rollout-*.jsonl" `
-ErrorAction SilentlyContinue
).Count
[pscustomobject]@{
CodexHome = $home
Active = $active
Archived = $archived
Total = $active + $archived
}
}
rollout-*.jsonlが存在すれば、少なくともセッション実体が残っている可能性がある。ただし、空ファイル、古い形式、内部実行などが混ざるため、件数だけで復元成功とは判断しない。
ファイルパスや日付フォルダーではなく、JSONLファイル名末尾のUUIDをスレッドIDとして重複判定する。Windows側の通常・アーカイブ両方に存在しないIDだけをコピーする。
無上書き統合用PowerShell
$knownIds = [System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::OrdinalIgnoreCase
)
foreach ($destination in @(
(Join-Path $WindowsCodexHome "sessions"),
(Join-Path $WindowsCodexHome "archived_sessions")
)) {
Get-ChildItem $destination -Recurse -Filter "rollout-*.jsonl" `
-ErrorAction SilentlyContinue |
ForEach-Object {
if ($_.Name -match
'([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})\.jsonl$') {
[void] $knownIds.Add($Matches[1])
}
}
}
function Copy-CodexRolloutsWithoutOverwrite {
param(
[Parameter(Mandatory)] [string] $SourceRoot,
[Parameter(Mandatory)] [string] $DestinationRoot,
[Parameter(Mandatory)] $KnownIds
)
if (-not (Test-Path -LiteralPath $SourceRoot)) {
return
}
New-Item -ItemType Directory -Path $DestinationRoot -Force |
Out-Null
$copied = 0
$skipped = 0
$sourcePrefixLength = $SourceRoot.TrimEnd('\').Length
Get-ChildItem $SourceRoot -Recurse -Filter "rollout-*.jsonl" |
ForEach-Object {
if ($_.Name -notmatch
'([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})\.jsonl$') {
return
}
$threadId = $Matches[1]
if ($KnownIds.Contains($threadId)) {
$skipped++
return
}
$relativePath = $_.FullName.Substring(
$sourcePrefixLength
).TrimStart('\')
$targetPath = Join-Path $DestinationRoot $relativePath
$targetDirectory = Split-Path $targetPath -Parent
New-Item -ItemType Directory -Path $targetDirectory -Force |
Out-Null
Copy-Item -LiteralPath $_.FullName -Destination $targetPath
[void] $KnownIds.Add($threadId)
$copied++
}
[pscustomobject]@{
Source = $SourceRoot
Destination = $DestinationRoot
Copied = $copied
SkippedExistingThreadId = $skipped
}
}
Copy-CodexRolloutsWithoutOverwrite `
-SourceRoot (Join-Path $WslCodexHome "sessions") `
-DestinationRoot (Join-Path $WindowsCodexHome "sessions") `
-KnownIds $knownIds
Copy-CodexRolloutsWithoutOverwrite `
-SourceRoot (Join-Path $WslCodexHome "archived_sessions") `
-DestinationRoot (Join-Path $WindowsCodexHome "archived_sessions") `
-KnownIds $knownIds
旧Windows CLI側にも独自セッションがある場合は、同じ関数を追加で呼ぶ。同じスレッドIDがWindows側の通常・アーカイブのどちらかにあれば上書きしない。
移行したJSONLは重要度の低い1件で直接検証する¶
サイドバーへ出なくても、スレッドIDを直接指定すると開ける場合がある。
codex resume <THREAD_ID>
codex resume --allが既存JSONLを一覧に出さない一方、IDを直接指定すると再開できる事例は、公式リポジトリのIssue #20165でも報告されている。5
最初は重要度の低い1件で実行し、履歴が開いたらプロンプトを送信せず終了する。 これで、少なくとも次の2点を分けて判断できる。
- Windows版Codexが移行後のJSONLを読める
- サイドバーに出ない原因は、会話実体より一覧索引側にある
サイドバー索引はapp-serverで再構築する¶
JSONLをコピーしただけでは、Windows側の状態DBにスレッド行がなく、 サイドバーへ表示されない場合がある。 Issue #34782でも、セッション実体が残っているのにサイドバー履歴が消え、 アプリ再起動で再索引されなかったWindows/WSL事例が報告された。6
検証環境では、Codexのapp-server APIで各スレッドをthread/resumeし、 通常スレッドの索引を再構築した。 これは推測上の回避策ではなく、1,000件超の復元で実際に使った処理である。
実行中のCodexに一致するSchemaを生成する¶
app-serverのREADMEは、実行中のCodexバージョンに一致するJSON Schemaを生成できると説明している。7
codex app-server generate-json-schema `
--experimental `
--out .\codex-app-server-schema
Schemaで少なくとも次を確認する。
thread/resumeの必須フィールドexcludeTurnsが利用できるかthread/archiveとthread/unarchiveの引数initialize.params.clientInfoの必須フィールド- 実験的APIの有効化方法
1件だけメタデータ中心に再開する¶
Codex CLI 0.144.4で確認した最小メッセージは次の形になる。
$threadId = "<THREAD_ID>"
$messages = @(
@{
id = 1
method = "initialize"
params = @{
clientInfo = @{
name = "sidebar-reindex-test"
title = "Sidebar reindex test"
version = "1.0.0"
}
capabilities = @{
experimentalApi = $true
}
}
},
@{
method = "initialized"
params = @{}
},
@{
id = 2
method = "thread/resume"
params = @{
threadId = $threadId
excludeTurns = $true
}
},
@{
id = 3
method = "thread/unsubscribe"
params = @{
threadId = $threadId
}
}
)
$messages |
ForEach-Object { $_ | ConvertTo-Json -Depth 10 -Compress } |
codex app-server --listen stdio://
excludeTurns = $trueは、会話全体をレスポンスへ展開せず、 メタデータと再開状態を返すために使う。 処理後はcodex doctorとサイドバーの両方で、 対象スレッドが索引化されたか確認する。
一括処理はレスポンス待機を実装する
上のパイプラインは1件の疎通確認用である。 複数IDを処理するクライアントは、initialize完了、各thread/resume応答、 thread/unsubscribe応答を順番に待つ必要がある。 成功・失敗を逐次ログへ保存し、タイムアウト時はapp-serverだけを再起動する。 全IDを応答待ちなしで標準入力へ流さない。
実際の一括復元では、次の安全策を入れた。
- 処理側がWindows版の
CODEX_HOMEを確認する sessionsからスレッドIDを列挙するthread/resumeを1件ずつ送る- 各レスポンス後に
thread/unsubscribeする - タイムアウトしたIDを記録し、app-serverを再起動する
- 成功・失敗をJSONLログへ逐次保存する
- SQLiteへ直接書き込まない
- 最後に
codex doctorで差分を確認する
アーカイブ索引は解除と再アーカイブで復元する¶
アーカイブJSONLは、コピーしただけではアーカイブ一覧へ出ない場合がある。検証環境では、各スレッドを一度解除して再アーカイブすると、JSONLを最終的にアーカイブ位置へ戻したまま、状態DBのアーカイブ行を再生成できた。
安定CLIが使える現在のバージョンでは、まず次を使う。
codex unarchive <THREAD_ID>
codex archive <THREAD_ID>
app-serverクライアントへ組み込む場合のメソッドは次の形になる。
{"id":10,"method":"thread/unarchive","params":{"threadId":"<THREAD_ID>"}}
{"id":11,"method":"thread/archive","params":{"threadId":"<THREAD_ID>"}}
必ずバックアップ後に重要度の低い1件で試し、次を確認してから一括処理へ進む。
sessions側に同じIDが重複して残っていないarchived_sessions側にJSONLが1件あるcodex doctorでアーカイブ不整合が増えていない- Windows版からアーカイブ一覧を開ける
全JSONL数とサイドバー件数は一致しない¶
Codexのthread/listは、sourceKindsを省略すると対話的なソースを既定対象にする。 生成済みSchemaでは、cli、vscode、exec、appServer、 複数のサブエージェント種別などが区別されている。 内部実行やサブエージェントのJSONLまで、 すべて通常サイドバーへ並ぶわけではない。7
したがって、全JSONL数 > 状態DBの有効行数 > 通常サイドバーへ表示される対話スレッド数 という大小関係は失敗を意味しない。
復元判定では、件数だけでなく次を見る。
- WSL由来の既知スレッドIDを直接再開できる
- そのIDが
thread/listまたはサイドバーへ返る - 通常セッションとアーカイブの所属が正しい
codex doctorで状態DBの整合性が正常である
検証環境では、通常セッションとアーカイブを合わせて1,000件超を索引化した。 missing active rows、missing archived rows、stale rows、 archive mismatchesはすべて0件になった。
古いJSONLの一部はno parseable rollout itemsと判定された。 該当ファイルは削除せずバックアップへ残し、 現在のCodexが読めるセッションだけを索引化した。
公開ログからセッション内容と秘密情報を除く¶
ブログ、GitHub Issue、Gistへログやコマンドを載せる前に、次を置換する。
| 公開しない値 | 置換後 |
|---|---|
| Windowsユーザー名 | <WINDOWS_USER> |
| WSLユーザー名 | <WSL_USER> |
| WSLディストリビューション | <DISTRO> |
| プロジェクト名 | <PROJECT> |
| スレッドID | <THREAD_ID> |
| ローカル絶対パス | <LOCAL_PATH> |
| メールアドレス | <EMAIL> |
| APIキー・トークン | <REDACTED> |
| 組織名・リポジトリ名 | <REPOSITORY> |
次のファイルは内容をそのまま公開しない。
auth.jsonconfig.toml.codex-global-state.jsonrollout-*.jsonllogs_*.sqlitestate_*.sqlite
JSONLには会話本文、コマンド、ファイルパス、プロジェクト情報が含まれ得る。OpenAIの公式資料も、ログを共有する前に機密情報がないか確認するよう案内している。8
復元完了はCodex自身の診断と実表示で判定する¶
最後にWindowsネイティブ側で診断する。
codex doctor --summary --no-color --ascii
復元完了の判定は、次の順に行う。
- Windows版Codexが状態DBを正常と診断する
- 移行した通常スレッドをサイドバーから開く
- 移行したアーカイブをアーカイブ一覧から開く
- 既存のWindows側スレッドが上書きされていない
- 既知のWSL側スレッドIDを直接再開できる
- scan errorが残るJSONLを削除せず、別途記録する
- 元のWSLデータとバックアップを当面保持する
この復元では、会話実体、状態DB、サイドバー表示を別々に検証する。 JSONLをコピーできただけでも、ファイル数が一致しただけでも完了ではない。 Windows版Codexが実際に一覧へ出し、 スレッドを開けるところまで確認して初めて復元済みと判断できる。
関連記事¶
OpenAI: ChatGPT desktop app for Windows — Share config, auth, and sessions with WSL ↩↩
openai/codex Issue #23251: WSL CLI cannot share Windows Codex App CODEX_HOME ↩
openai/codex Issue #20165: Direct resume works while resume --all misses sessions ↩
openai/codex Issue #34782: WSL path resolution and missing sidebar history ↩