docs/guides/codex-unified-session-history-guide-en.md
Applies to CC Switch v3.16.x and later. This guide is based on the current code; every command and path can be verified by hand. Examples use de-identified data and contain no real session content or API keys.
"Unified Codex session history" is a switch that CC Switch v3.16.x adds for Codex. You'll find it under Settings -> General -> the "Codex App Enhancements" group ("Codex App Enhancements" is the group title; the switch itself is called "Unified Codex session history"). Once enabled, sessions from your official subscription (ChatGPT login / OpenAI API key) appear in the same history / resume list as sessions from every third-party provider CC Switch manages—they are no longer split into two lists that can't see each other.
Codex classifies sessions by a "provider tag" (a field called model_provider), and the resume / history list only shows sessions whose tag matches your currently active provider. As a result, sessions are naturally sorted into two separate "drawers":
openai tag;custom tag.The two drawers can't see each other. If you switch frequently between official and third-party, you'll hit this kind of fragmentation: "the session I was just chatting in with the official account disappeared from the history list after I switched to a third-party provider"—it isn't actually gone, it's just been sorted into the other drawer. This split both makes it easy to believe a session was lost, and makes it inconvenient to review and resume all your sessions in one place.
This switch exists to eliminate that fragmentation: it makes the official subscription run under the custom tag too, so official and third-party sessions merge into one list and everything is easy to find and resume in a single place.
✅ One important premise that runs through this whole guide, please remember it first: this feature (unify / migrate / restore) only ever rewrites that one classification tag
model_providerin your session records, and it automatically makes a backup of the original file before every rewrite. It never deletes, clears, or overwrites a single line of your conversations. So whenever this guide later mentions "some sessions are no longer visible," it almost always means "they've been sorted into the other drawer," not "the data is gone." If you're truly worried, jump straight to the symptom reference table and verify the files are still there by hand.
Think of it as two drawers + automatic backup:
openai drawer and third-party sessions live in the custom drawer, invisible to each other;custom drawer too, merging the two drawers into one shared list;For the full mechanism (what gets injected, why it's reversible, how migration / restore guarantee no data loss) see The core mental model and the Advanced mechanism appendix at the end.
To understand this feature, you only need to remember two things: drawers and backups.
Every time you start a Codex session, Codex records a tag model_provider in the session file header, marking "which provider this session was chatted with." Codex's resume / history list is filtered precisely by the currently active tag—it only shows sessions whose tag matches "the provider you're on right now."
openai.custom.So by default, official sessions and third-party sessions are inherently invisible to each other—they live in two different drawers. This is Codex's own design, not CC Switch losing anything.
Default state (unified switch off):
┌───────────────────────┐ ┌──────────────────────────┐
│ openai drawer │ │ custom drawer │
│ (official sessions) │ │ (third-party sessions) │
└───────────────────────┘ └──────────────────────────┘
▲ ▲
visible only while visible only while
on the official provider on a third-party provider
The two drawers can't see each other.
What the "Unified Codex session history" switch does is make the official subscription run under the custom tag too, merging the two drawers into one, so official and third-party sessions appear in the same resume list. Note: authentication doesn't change—your official subscription still uses your ChatGPT login and still goes through the official backend; only the session's "classification tag" changes from openai to custom.
After the unified switch is on:
┌──────────────────────────────────────────────┐
│ custom shared drawer │
│ official sessions + third-party sessions │
│ (appear in the same history / resume list) │
└──────────────────────────────────────────────┘
"Merging the drawers" requires changing the tag of some official sessions from openai to custom (this step is called migration, and it's optional and requires you to opt in). And before any rewrite, CC Switch first copies the original file untouched to here:
~/.cc-switch/backups/codex-official-history-unify-v1/<timestamp>/
This backup is the sole basis for "restore exactly from backup" later. It makes the whole process reversible: at any time you can turn off the switch and precisely flip the official sessions you migrated in back to the openai drawer.
Remember these two words—drawer (a session just gets reclassified) and backup (a copy is always made before a change)—and everything that follows will be easy to understand.
Settings -> General -> Codex App Enhancements
In the "Codex App Enhancements" block there are two rows of switches; the second row (the blue history icon) is the subject of this guide:
Unified Codex session history
Below it is a line of description text (verbatim):
When enabled, the official subscription runs under the shared "custom" provider id so official and third-party sessions appear in one history list, optionally migrating existing official sessions in (backed up first). When turning it off, the migrated sessions can be restored from backup. Note: resuming an old session across providers may fail because its encrypted_content reasoning can only be decrypted by the backend that created it.
Note: this single line of description already previews three things—sessions will appear in one list, you can optionally migrate them in with an automatic backup, and resuming across providers "may fail." Here, "fail" means you can't resume / can't generate a new turn, not "the record is lost." This is exactly the core misunderstanding we'll dig into below.
The moment you flip the switch on, CC Switch does not save immediately; instead it first pops up a confirmation dialog. The dialog text reads as follows (verbatim):
Title: Unified Codex session history
Body:
When enabled, the official subscription and third-party providers share one session history list. Note: resuming an old session across providers may fail because its encrypted_content reasoning cannot be decrypted by another backend.
You can also migrate your existing official session history into the shared list (originals are backed up to ~/.cc-switch/backups first and can be restored when you turn this off).
Checkbox: Also migrate existing official session history
Confirm button: I understand, enable
Cancel button: Cancel
This checkbox is unchecked by default. This is an important fork in the road:
| Your choice | Effect | Where your data is right now |
|---|---|---|
| Unchecked (default) | Only switches the tag. Only official sessions created after enabling land in the custom shared drawer | Your official sessions from before enabling keep the openai tag, stay exactly where they were, still in ~/.codex/sessions/ |
| Checked | In addition to switching the tag, also migrates your existing official sessions from the openai drawer into the custom drawer | After being copied to backup, the old sessions' tag is rewritten to custom; the original data is covered by the backup |
If you want "my earlier official sessions to appear in the unified list too," you must opt in by checking this box. Otherwise you'll run into "scenario A" in the reference table below—the old sessions look "gone," when in fact they're just sitting in the original drawer.
Click "Cancel" or click outside the dialog: the switch flips straight back to off and nothing happens. Click "I understand, enable": the switch is saved as on, and CC Switch persists the configuration in the background (and runs the migration if you checked it).
If you check "Also migrate existing official session history," CC Switch runs this procedure on your existing official sessions:
For each official (openai tag) session file:
① First copy the original file untouched into the backup directory <- data now has its first safety net
② Using "write a temp file -> replace the whole thing" atomic style,
change only the model_provider in the session_meta line at the header
from "openai" to "custom" <- not a single byte of the conversation body is touched
③ Update the index database state_5.sqlite to switch the tag in the same transaction
~/.cc-switch/backups/codex-official-history-unify-v1/<timestamp>/. Each migration produces one timestamped "generation directory," containing jsonl/ (session copies), state/ (index DB copy), and meta.json (recording which Codex directory this migration belongs to).model_provider. Your conversation content, reasoning content, and all body text are kept exactly as is.After a successful migration, these existing official sessions show up in the unified list. At this moment your data is: ① the original copy in the backup directory; ② in the active file, only the classification tag changed, the content intact.
Note: enabling and migration themselves do not pop a success toast. Migration runs as a side task on the backend during save; in the UI you'll only see the switch turn on. So "I didn't see a migration-success popup" is normal and does not mean failure.
When disabling, CC Switch first spends a moment probing whether there's a migration backup, then pops up a confirmation dialog (so the disable dialog has a slight delay, which is normal). The text reads as follows (verbatim):
Title: Turn off unified session history
Body:
After turning this off, the official subscription and third-party providers return to separate history lists. Sessions created while it was on cannot be attributed to a provider, so they stay in the third-party history and the official subscription will not see them.
Checkbox (shown conditionally): Restore the official sessions migrated at enable time back to the official history (exact restore from backup)
Confirm button: Turn off
Cancel button: Cancel
Key point: the body says the official subscription will not see them—won't see, not delete. The new sessions you chatted during the unified period are still fully present in the
customdrawer; after disabling, the official side simply won't see them.
This restore checkbox is checked by default. In other words, the default behavior is "restore the official sessions you migrated in back to the official history at the same time you disable." You only need to keep it checked and click "Turn off."
If the checkbox doesn't appear, the system has determined there's no backup that needs restoring (either you never checked migration, or no backup was found)—in that case your existing official sessions were never touched, and turning off the switch returns them to the openai drawer on their own.
If you keep the box checked and click "Turn off," CC Switch's restore flow goes like this:
① First copy the current state once more into a separate restore-backup directory
~/.cc-switch/backups/codex-official-history-unify-restore-v1/<timestamp>/
(restore itself backs up first, so restore won't lose data either)
② Comb through all migration backup generations, find the session ids "whose tag was originally openai," and assemble a "ledger"
③ Only for sessions that are [both in the ledger AND currently still custom], change the tag back to "openai"
Note the dual condition in step ③—it must be in the ledger (proving it really was migrated from the official side) AND currently still custom (showing you haven't manually changed it). Only when both conditions hold does it get flipped back. This guarantees the restore is both precise and free of collateral damage.
At this moment your data is: the migrated-back official sessions have their tag changed back to openai and reappear in the official list; meanwhile both the migration backup and the restore backup copies are still on disk.
Only the "disable + check restore" path pops a result toast. The toasts you may see (verbatim):
| Toast you see | Meaning |
|---|---|
| Official session history restored from backup ({{files}} session files, {{rows}} index rows) | Restore succeeded. {{files}} / {{rows}} show the actual numbers |
| No restorable migration backup for the current Codex directory | Nothing to restore (does not mean data is lost, see scenario E in the reference table) |
| Unified session history was re-enabled; restore skipped | You turned the switch back on while restore was queued, so the system deliberately abandoned the restore (see scenario F) |
| Failed to restore official session history, please try again | The restore process errored; just retry, the data is not corrupted |
| Save failed, please try again | The disable save itself failed; in this case restore is never triggered and the switch flips back to its original position |
A thoughtful safety design: if the "disable the switch" save fails, CC Switch never runs the restore. Otherwise you'd end up in a torn state of "switch still on, but sessions flipped back to the openai bucket." When the save fails, the switch automatically flips back to its original position, so you won't be stuck in a fake state of "looks off but didn't actually save."
The six scenarios below are the situations where users most easily believe "sessions are gone." The truth in every one is: the data is intact, it just moved drawers or is temporarily out of sight. Use this table to locate your symptom first, then read the detailed explanation below.
| Scenario | What you see | The data truth | One-line fix |
|---|---|---|---|
| A Didn't check migration | Old official sessions not in the unified list | All present, still carry the openai tag | Re-enable and check migration, or turn off the switch |
| B Cross-provider resume fails | Can't resume / errors out | Files intact, the ciphertext just can't be decrypted across backends | Resume on the original provider; to only read content, read the jsonl directly |
| C Proxy takeover / injection refused | No migration and no restore | Migration was safely skipped, files untouched | Exit takeover -> restart and retry; or just turn off the switch |
| D New sessions didn't return to official after restore | New sessions from the unified period aren't on the official side | They're in the custom drawer, untouched by design | Switch to a third-party provider to see them |
| E Toast "no restorable backup" | Restore "failed" | Usually nothing was ever migrated, sessions are in the original drawer | Turn off the switch and the official sessions reappear automatically |
| F Toast "switch was re-enabled, restore skipped" | Restore refused | Prevents a torn data state, nothing was changed | Fully turn off the switch first, then restore |
Symptom: you turned on the unified switch, but didn't check "Also migrate existing official session history" in the enable dialog (it's unchecked by default). After enabling, your earlier official sessions seem to be gone from the list.
The truth: 100% of your data is present, not a single line moved. The switch only takes effect on official sessions "created after enabling"; your official sessions from before enabling still carry the openai tag and sit untouched in ~/.codex/sessions/. You're now on the custom drawer, so naturally you can't see the old sessions left in the openai drawer—that's the entire reason for the "apparent disappearance."
What to do (pick either):
custom drawer and they immediately appear in the unified list (automatic backup before the rewrite).openai drawer again, and the old sessions reappear right where they were.Symptom: after unification, the list shows an old session chatted with "another provider." You switch to your current provider and click "Resume," but it errors out or can't connect.
The truth: the session file is intact; what's lost is not data, it's "cross-backend decryption ability." A Codex session stores an encrypted block of reasoning content encrypted_content, and this ciphertext can only be decrypted by the backend that originally generated it. Using provider B to resume a session generated by provider A means B can't decrypt A's ciphertext -> resume fails. This is a design limitation of upstream Codex (by design) and has nothing to do with whether CC Switch touched the file. The text content of the session is readable at any time.
This is the only "looks like a real problem" genuine exception in this whole guide—but note: it just means you can't resume (can't generate a new turn), and the original file is still fully present, the conversation text readable at any time.
What to do:
.jsonl file directly (commands at the end).Symptom: you enabled the switch and checked migration, but the old official sessions neither entered the unified list nor could be restored when you turned the switch off (or the restore checkbox didn't even appear in the disable dialog, see scenario E). You suspect migration lost the sessions during the process.
The truth: migration never ran, so it couldn't have lost anything—not a single character of your sessions was changed. CC Switch has a safety gate before migration: it checks whether Codex's live config (~/.codex/config.toml) is actually routed to the shared custom drawer right now, and only migrates if the routing truly went there. The following two situations are judged "not yet unified" (internal reason code live_not_unified), so CC Switch deliberately skips the migration, preserves your switch and migration intent, and migrates later once the conditions are met:
config.toml already has a manually specified model_provider, or there's already a differently-shaped [model_providers.custom] table (possibly with a third-party address). To avoid incorrectly routing official traffic to a third-party backend, CC Switch would rather not inject and not migrate.Skipping migration = touching no session files. No migration means nothing moved, so there's nothing to lose. This is "safe deferral," not "failure with data loss."
What to do:
~/.codex/config.toml: if there's a conflicting route you wrote by hand, clean up the conflict before enabling the switch.openai drawer, completely intact.Symptom: during the unified period, you chatted a few more new sessions with the official account. Later you turned off the switch, checked restore, and after restoring you find those new sessions didn't return to the official drawer.
The truth: this is intentional design; the new sessions are perfectly fine in the custom drawer, visible and resumable. Restore is based on "the backup ledger from migration time"—only sessions that were originally migrated in from the openai drawer are recorded in the backup and get precisely flipped back to openai. The sessions you created during the unified period are in no backup ledger; and after unification both official and third-party use the custom tag, so CC Switch can't tell whether a new session was chatted with the official account or a third-party. To avoid wrongly stuffing third-party sessions into the official history, the product decision is: these new sessions all stay in the custom (third-party) history and are never moved automatically. The disable dialog's text says this explicitly too—"Sessions created while it was on cannot be attributed to a provider, so they stay in the third-party history."
What to do:
custom drawer) to see these sessions in the history list..jsonl directly; to resume, follow scenario B's rule (go back to the backend that originally generated it).model_provider in the session_meta of the first line of its .jsonl from custom back to openai (an advanced operation; always make a copy before editing).Symptom: you checked restore when turning off the switch, and got the toast "No restorable migration backup for the current Codex directory." You panic: restore failed, is the data completely gone?
The truth: "nothing to restore" ≠ "data is lost." On the contrary, it's usually because there was no migration that needed restoring. Common reasons:
openai drawer all along and reappear after you turn off the switch (same as scenario A). (In this case, the disable dialog may not even show the restore checkbox—because the system can't find any backup.)openai, so clicking again naturally finds "no targets still in custom to restore"—this is idempotent protection, not failure.In all three cases, no session was deleted.
What to do: use the end-of-guide commands to count the total session files in ~/.codex/sessions/ and confirm the files are all there; then check whether ~/.cc-switch/backups/ contains a codex-official-history-unify-v1 directory—if even this directory is absent, you never triggered a migration and the sessions have been in their original drawer all along.
Symptom: you turned off the switch -> checked restore -> but you were quick and immediately turned the switch back on, then saw the toast "Unified session history was re-enabled; restore skipped."
The truth: this is a safeguard against putting your data into a "torn" state, and again no sessions are lost. The restore action is "flip session tags from custom back to openai," but if the switch is on again at this moment, the live config is routing to custom—flipping history back to openai on one side while new sessions land in custom on the other would artificially tear sessions in two. So when CC Switch detects "the switch is on again," it deliberately abandons this restore and changes nothing. Sessions stay as they are, with no deletion or corruption.
What to do: to truly restore, turn the switch off and keep it off (don't immediately turn it back on), then do disable + check restore; to keep things unified, don't restore, and let the sessions stay in the custom shared drawer for normal use.
The overriding principle: CC Switch's unify / migrate / restore only ever changes a single tag field in a session, and automatically backs up before every rewrite. It never deletes your conversations. Out of sight ≠ gone—look in the other drawer, or use the commands below to confirm with your own eyes.
No amount of text beats seeing it for yourself. Below are the real paths (taken from the CC Switch source) and how to view session files and backup directories on different systems. The whole process is read-only and changes nothing; you're strongly encouraged to try it by hand.
Cmd + Shift + G, paste ~/.codex/sessions and hit Enter to see a pile of .jsonl session files and their modification times; for the backup directory paste ~/.cc-switch/backups.%USERPROFILE%\.codex\sessions into the address bar and hit Enter to see the session folders and the .jsonl files inside; for the backup directory paste %USERPROFILE%\.cc-switch\backups.As long as you can see a batch of .jsonl files here, that proves your session data is intact on disk. The file count and modification times are more intuitive than any amount of text.
| Content | Real path | Notes |
|---|---|---|
| Session body (the core) | ~/.codex/sessions/ (includes date-based subdirectories, recursive) | One .jsonl text file per session—this is your conversation content |
| Archived sessions | ~/.codex/archived_sessions/ | Also .jsonl |
| Session index database | ~/.codex/state_5.sqlite | The model_provider column of the threads table is the "drawer tag"—this is the actual classification source the resume list reads |
| Migration backup (auto-created when migration is enabled) | ~/.cc-switch/backups/codex-official-history-unify-v1/<timestamp>/ | Contains jsonl/, state/, meta.json |
| Restore backup (auto-created when you restore) | ~/.cc-switch/backups/codex-official-history-unify-restore-v1/<timestamp>/ | A safety copy taken before restore |
Note: if you've changed the Codex directory in CC Switch, or set
sqlite_homeinconfig.toml, replace~/.codexabove with your actual directory. Below,~= your user home directory.
1. Count the total number of session files (this is the hard evidence of "nothing lost")
# Count the total number of session files -- as long as this number matches your expectation, the data is all there
find ~/.codex/sessions ~/.codex/archived_sessions -name '*.jsonl' 2>/dev/null | wc -l
# Show the 10 most recently modified session files
find ~/.codex/sessions -name '*.jsonl' 2>/dev/null -print0 \
| xargs -0 ls -lt 2>/dev/null | head -10
2. (Auxiliary) See how many sessions are in each "drawer"
# Number of session files in the official drawer (openai)
grep -rlE '"model_provider"[[:space:]]*:[[:space:]]*"openai"' ~/.codex/sessions 2>/dev/null | wc -l
# Number of session files in the unified drawer (custom)
grep -rlE '"model_provider"[[:space:]]*:[[:space:]]*"custom"' ~/.codex/sessions 2>/dev/null | wc -l
# See the tag distribution at a glance
grep -rhoE '"model_provider"[[:space:]]*:[[:space:]]*"[^"]*"' ~/.codex/sessions 2>/dev/null | sort | uniq -c
Important note, don't let this step scare you: early versions of Codex did not write the
model_providerfield into the.jsonl, so these old official sessions can't be counted by the grep above—but they're still classified asopenaiin the index databasestate_5.sqliteand still show up in the resume list. So judge "nothing lost" by the total file count from step 1—the per-drawer grep is only there to help you understand the classification, and counting fewer than the total file count is completely normal and never means "a batch was lost."
3. (Advanced) Query the index database state_5.sqlite—the classification the resume list actually reads
# Requires sqlite3 to be installed; skip if you don't have it
sqlite3 ~/.codex/state_5.sqlite \
"SELECT COALESCE(model_provider,'<empty>'), COUNT(*) FROM threads GROUP BY 1;"
This
threadstable is the actual classification source Codex's resume list reads; theopenairow count ≈ the number of sessions you can see in your official drawer. It may not match step 2's jsonl grep—the reason is exactly what's described above: "old sessions don't write the jsonl field, but they're still openai in the index database." A mismatch between the two is not an anomaly.
4. Read the content of a specific session directly (confirm the conversation text is still there)
# Replace <filename> with one of the .jsonl paths listed by ls above
python3 -m json.tool < "<filename>.jsonl" 2>/dev/null | head -50
# Or just open it in an editor (plain text)
open -e "<filename>.jsonl" # macOS
5. Look at CC Switch's backup directory (proof that a copy was kept before migration / restore)
ls -la ~/.cc-switch/backups/codex-official-history-unify-v1/ 2>/dev/null
ls -la ~/.cc-switch/backups/codex-official-history-unify-restore-v1/ 2>/dev/null
The session directory is usually at C:\Users\<your username>\.codex\, and backups at C:\Users\<your username>\.cc-switch\backups\.
# 1. Total number of session files (hard evidence of "nothing lost")
(Get-ChildItem "$env:USERPROFILE\.codex\sessions","$env:USERPROFILE\.codex\archived_sessions" -Recurse -Filter *.jsonl -ErrorAction SilentlyContinue).Count
# 2. The 10 most recently modified sessions
Get-ChildItem "$env:USERPROFILE\.codex\sessions" -Recurse -Filter *.jsonl |
Sort-Object LastWriteTime -Descending | Select-Object -First 10 FullName,LastWriteTime
# 3. (Auxiliary) How many session files in the official (openai) / unified (custom) drawers
(Get-ChildItem "$env:USERPROFILE\.codex\sessions" -Recurse -Filter *.jsonl |
Select-String -Pattern 'model_provider"\s*:\s*"openai"' -List).Count
(Get-ChildItem "$env:USERPROFILE\.codex\sessions" -Recurse -Filter *.jsonl |
Select-String -Pattern 'model_provider"\s*:\s*"custom"' -List).Count
# 4. Look at the backup directories
Get-ChildItem "$env:USERPROFILE\.cc-switch\backups\codex-official-history-unify-v1" -ErrorAction SilentlyContinue
Get-ChildItem "$env:USERPROFILE\.cc-switch\backups\codex-official-history-unify-restore-v1" -ErrorAction SilentlyContinue
Same reminder: the step-3 grep counting fewer than the total file count is normal (old sessions don't write that field); judge "nothing lost" by the total file count from step 1.
Codex's resume / history list filters by the currently active model_provider id with exact string matching. The first line of a session's .jsonl file is a type:"session_meta" record whose payload.model_provider is the drawer that session belongs to (grep -rl counts a file as long as the tag appears once anywhere in it, so no line-by-line parsing is needed; sessions from old versions that didn't write the field can't be counted). What actually drives the resume list is the threads.model_provider column of the index database state_5.sqlite. When config.toml has no explicit model_provider, the official subscription falls into the built-in default id openai; all of CC Switch's third-party providers uniformly use custom.
When enabled, CC Switch injects the following into the official live config.toml:
model_provider = "custom"
[model_providers.custom]
name = "OpenAI"
requires_openai_auth = true
supports_websockets = true
wire_api = "responses"
Every field has a purpose: requires_openai_auth = true keeps authentication going through the ChatGPT login in auth.json, with the base_url defaulting back to the official Codex backend; name = "OpenAI" lets Codex's official feature gates (web search, remote compaction, etc.) keep matching; supports_websockets = true restores the capability that custom entries lose by default; wire_api = "responses" uses the official responses protocol. The net effect is: authentication is unchanged, only the bucket name changed.
Key invariant: this injection can only exist in the live config.toml, and is never written into the database's stored configuration. When you switch away from the official provider and write live back to the database, CC Switch strips this injection precisely (it strips only when the shape exactly matches the injected artifact; a third-party-customized custom table is kept as is). Precisely because of this, "turning off the switch + switching once" fully restores live, and the database always holds your original clean official configuration—this is the cornerstone of the whole switch's reversibility.
config.toml already has an explicit model_provider -> don't override the user's route;[model_providers.custom] table already exists (possibly with a third-party base_url) -> refuse injection, otherwise ChatGPT OAuth traffic would be routed to the wrong backend.When injection is refused, live is not unified, and the migration gate (checking whether live's model_provider equals custom after trim) judges live_not_unified -> skip migration, preserve intent, and do it later on the next startup retry. This is "safe deferral," not "failure with data loss."
openai;custom);Four layers of design jointly guarantee that under all paths, normal and abnormal, the original session data is never truly deleted.
model_provider value in session metadata between openai and custom; conversation content, response_item, and encrypted_content are all kept exactly as is.codex-official-history-unify-v1/, restore backups in the separate codex-official-history-unify-restore-v1/—the two are kept apart to keep the ledger clean.UPDATE, with no deletion of any session or index at any point. The file is complete at every moment.live_not_unified), it would rather not migrate; a single process lock serializes migration and restore to avoid "startup retry / post-save background task / disable-time restore" concurrently rewriting the same batch of files in both directions; the completion marker is bound to the Codex directory and written conditionally to prevent missed migrations; restore uses the "in the ledger + currently still custom" dual condition to prevent wrong changes. Restore scans the union of all backup generations, so even after many switch cycles it can still restore early-migrated sessions; a repeated restore returns nothing_to_restore, which is idempotent protection rather than failure.The reasoning ciphertext inside a session can only be decrypted by the backend that generated it; upstream Codex by design does not support cross-backend decryption. This is the root cause of "resume failure" and has nothing to do with file integrity—the session .jsonl sits fully on disk and encrypted_content is intact too. Switching back to the original provider to resume, or starting a new session, both work fine.
One last word for you: what you see as "sessions disappeared / resume failed" is essentially the session being moved to another history list (drawer), or the other backend being unable to decrypt the old reasoning content; the files always sit untouched in ~/.codex/sessions/ (and state_5.sqlite). Checking "restore from backup" when you turn off the switch precisely flips the official sessions you migrated in back to the official list; and even if you don't restore, both the original .jsonl files and the backup copies under ~/.cc-switch/backups/codex-official-history-unify-*/ are all still there—the data is never truly lost.