mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
docs(guides): add trilingual Codex unified session-history guide
New zh/en/ja guide for the unified Codex session-history toggle: what opt-in migration (on enable) and ledger-based restore (on disable) actually do, why session data is never truly deleted (tag-only rewrite + automatic backups), and how to verify files on disk versus just being filed under another provider drawer. Includes a symptom reference table for the common "my sessions are gone" misunderstanding and on-disk verification commands for macOS/Linux/Windows. Link the guide as the lead item in the "Usage Guides" section of the v3.16.3 release notes (zh/en/ja).
This commit is contained in:
@@ -0,0 +1,467 @@
|
||||
# Unified Codex Session History: Feature Overview and Usage Guide (CC Switch)
|
||||
|
||||
> 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.
|
||||
|
||||
## What this feature is
|
||||
|
||||
"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.
|
||||
|
||||
## What problem it solves
|
||||
|
||||
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":
|
||||
|
||||
- Sessions from your official subscription go under Codex's built-in **`openai`** tag;
|
||||
- Every third-party provider CC Switch manages goes under the **`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_provider` in 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](#i-feel-like-my-sessions-are-gone-symptom-reference-table) and [verify the files are still there by hand](#verify-by-hand-your-session-files-are-still-on-disk-the-most-important-section).
|
||||
|
||||
## How it works (one-line version)
|
||||
|
||||
Think of it as **two drawers + automatic backup**:
|
||||
|
||||
- By default, official sessions live in the `openai` drawer and third-party sessions live in the `custom` drawer, invisible to each other;
|
||||
- The switch makes **the official side use the `custom` drawer too**, merging the two drawers into one shared list;
|
||||
- You can optionally choose to "move" your **existing official sessions** into the shared drawer as well (this step is called **migration**; it's optional and requires you to opt in by checking a box), and **before anything is moved a backup copy is made first**, so the whole process is **reversible**;
|
||||
- **Authentication is completely unaffected**—your official subscription still uses your ChatGPT login and still goes through the official backend; only the session's classification tag changes.
|
||||
|
||||
For the full mechanism (what gets injected, why it's reversible, how migration / restore guarantee no data loss) see [The core mental model](#the-core-mental-model-two-drawers--automatic-backup) and the [Advanced mechanism appendix](#advanced-mechanism-appendix-for-users-who-want-to-truly-understand-how-it-works) at the end.
|
||||
|
||||
## How to use it (at a glance)
|
||||
|
||||
1. **Enable**: Settings -> General -> Codex App Enhancements -> turn on "Unified Codex session history" -> in the dialog decide whether to check "Also migrate existing official session history" (check it if you want your **earlier** official sessions merged into the unified list too; leave it unchecked if you only want unification from now on) -> confirm. See [What happens when you enable it](#what-happens-when-you-enable-it-step-by-step).
|
||||
2. **Disable**: turn the same switch off -> in the dialog keep "restore exactly from backup" checked (it's checked by default) -> confirm, and the official sessions you migrated in will be precisely flipped back to the official list. See [What happens when you disable it](#what-happens-when-you-disable-it-step-by-step).
|
||||
3. **Feel like a session is gone?** Don't panic—jump to the [symptom reference table](#i-feel-like-my-sessions-are-gone-symptom-reference-table) to locate it by symptom, and use the commands in the [verify by hand](#verify-by-hand-your-session-files-are-still-on-disk-the-most-important-section) section to see for yourself that the files are all there.
|
||||
|
||||
---
|
||||
|
||||
## The core mental model: two drawers + automatic backup
|
||||
|
||||
To understand this feature, you only need to remember two things: **drawers** and **backups**.
|
||||
|
||||
### Drawers: how Codex classifies sessions
|
||||
|
||||
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."
|
||||
|
||||
- Sessions from your official subscription (ChatGPT login / OpenAI API key) carry the built-in tag **`openai`**.
|
||||
- Every third-party provider CC Switch manages uses the tag **`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.
|
||||
|
||||
```text
|
||||
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`.
|
||||
|
||||
```text
|
||||
After the unified switch is on:
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ custom shared drawer │
|
||||
│ official sessions + third-party sessions │
|
||||
│ (appear in the same history / resume list) │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Backups: a copy is made before every tag change
|
||||
|
||||
"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:
|
||||
|
||||
```text
|
||||
~/.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.
|
||||
|
||||
---
|
||||
|
||||
## What happens when you enable it: step by step
|
||||
|
||||
### Step 1: Find the switch
|
||||
|
||||
```text
|
||||
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.
|
||||
|
||||
### Step 2: Flip the switch from off to on -> a confirmation dialog pops up
|
||||
|
||||
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).
|
||||
|
||||
### Step 3 (only if you checked migration): how migration runs + data safety
|
||||
|
||||
If you check "Also migrate existing official session history," CC Switch runs this procedure on your existing official sessions:
|
||||
|
||||
```text
|
||||
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
|
||||
```
|
||||
|
||||
- **Backup location**: `~/.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).
|
||||
- **What's changed**: only the value of the single field `model_provider`. Your conversation content, reasoning content, and all body text are **kept exactly as is**.
|
||||
- **What's deleted**: **nothing**. The backup is a "copy," the rewrite is an "atomic replacement of the same file," and at no point is any session or index deleted. The file is complete at every moment (either the old content or the new content, never empty or half-written).
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## What happens when you disable it: step by step
|
||||
|
||||
### Step 1: Flip the switch from on to off -> probe for backups -> a confirmation dialog pops up
|
||||
|
||||
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 `custom` drawer; 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.
|
||||
|
||||
### Step 2: How restore runs (precise flip-back per the backup ledger)
|
||||
|
||||
If you keep the box checked and click "Turn off," CC Switch's restore flow goes like this:
|
||||
|
||||
```text
|
||||
① 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.
|
||||
|
||||
### Step 3: Read the toast, confirm the result
|
||||
|
||||
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."
|
||||
|
||||
---
|
||||
|
||||
## "I feel like my sessions are gone?" symptom reference table
|
||||
|
||||
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 |
|
||||
|
||||
### Scenario A: You enabled the switch but didn't check migration -> old official sessions "disappear"
|
||||
|
||||
**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):
|
||||
1. **Re-enable the switch and check "Also migrate existing official session history,"** which moves the old sessions to the `custom` drawer and they immediately appear in the unified list (automatic backup before the rewrite).
|
||||
2. **Or simply turn off the unified switch**, the official side runs on the `openai` drawer again, and the old sessions reappear right where they were.
|
||||
|
||||
### Scenario B: Cross-provider resume of an old session fails -> you think "this session is broken / gone"
|
||||
|
||||
**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**:
|
||||
- **Resume with "the provider that originally created this session,"** so it can decrypt normally and connect.
|
||||
- Just want to read the history without continuing? Read that session's `.jsonl` file directly (commands at the end).
|
||||
- Rule of thumb: **cross-provider is better suited to "starting a new session"; resume old sessions on their original provider whenever possible.**
|
||||
|
||||
### Scenario C: You enabled the switch and checked migration, but migration was silently skipped -> you think "migration lost the sessions"
|
||||
|
||||
**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**:
|
||||
|
||||
- **During proxy takeover**: CC Switch's proxy has taken over the live config, and the live config during takeover doesn't carry the unified routing marker.
|
||||
- **Injection refused**: your `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**:
|
||||
- Exit proxy takeover -> **restart CC Switch**: on startup it automatically retries migration (your migration intent is preserved the whole time).
|
||||
- Check `~/.codex/config.toml`: if there's a conflicting route you wrote by hand, clean up the conflict before enabling the switch.
|
||||
- If you'd rather not bother: just turn off the switch, the official sessions still display normally on the `openai` drawer, completely intact.
|
||||
|
||||
### Scenario D: You turned off the switch and restored, but "the new sessions chatted during the unified period" didn't return to official -> you think "the new sessions are gone"
|
||||
|
||||
**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**:
|
||||
- Switch to any third-party provider (the `custom` drawer) to see these sessions in the history list.
|
||||
- To read content, read the `.jsonl` directly; to resume, follow scenario B's rule (go back to the backend that originally generated it).
|
||||
- If you really want to manually return **one specific** session to official: there's currently no automatic button (deliberately omitted, to avoid misjudging the direction). Advanced users can, **after backing up** that file first, manually change `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).
|
||||
|
||||
### Scenario E: Restore toast "No restorable migration backup for the current Codex directory" -> you think "restore failed = data is gone"
|
||||
|
||||
**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:
|
||||
|
||||
- **You never checked "migrate existing official sessions" in the first place**: with no migration, there's naturally no migration backup and no sessions to flip back. Your old official sessions have been in the `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.)
|
||||
- **You've already restored once**: the session tags have all been flipped back to `openai`, so clicking again naturally finds "no targets still in custom to restore"—this is **idempotent protection, not failure**.
|
||||
- **You switched Codex directories**: restore only recognizes the backup ledger belonging to the **current** directory; switch directories and it can't find the old directory's ledger. Just switch the directory back.
|
||||
|
||||
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.
|
||||
|
||||
### Scenario F: Restore refused, toast "Unified session history was re-enabled; restore skipped"
|
||||
|
||||
**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.**
|
||||
|
||||
---
|
||||
|
||||
## Verify by hand: your session files are still on disk (the most important section)
|
||||
|
||||
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.**
|
||||
|
||||
### The simplest way: open it directly in a file manager (no command line at all)
|
||||
|
||||
- **macOS (Finder)**: press `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`.
|
||||
- **Windows (File Explorer)**: paste `%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.
|
||||
|
||||
### Where exactly your session / history files live
|
||||
|
||||
| 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_home` in `config.toml`, replace `~/.codex` above with your actual directory. Below, `~` = your user home directory.
|
||||
|
||||
### macOS / Linux commands
|
||||
|
||||
**1. Count the total number of session files (this is the hard evidence of "nothing lost")**
|
||||
|
||||
```bash
|
||||
# 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"**
|
||||
|
||||
```bash
|
||||
# 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_provider` field into the `.jsonl`**, so these old official sessions **can't be counted** by the grep above—but they're still classified as `openai` in the index database `state_5.sqlite` and 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**
|
||||
|
||||
```bash
|
||||
# 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 `threads` table is the actual classification source Codex's resume list reads; the `openai` row 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)**
|
||||
|
||||
```bash
|
||||
# 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)**
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
### Windows commands (PowerShell)
|
||||
|
||||
The session directory is usually at `C:\Users\<your username>\.codex\`, and backups at `C:\Users\<your username>\.cc-switch\backups\`.
|
||||
|
||||
```powershell
|
||||
# 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.
|
||||
|
||||
---
|
||||
|
||||
## Advanced mechanism appendix (for users who want to truly understand how it works)
|
||||
|
||||
### 1. The bucketing mechanism (the essence of the drawers)
|
||||
|
||||
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`.
|
||||
|
||||
### 2. What the switch does (injection, lives only in live)
|
||||
|
||||
When enabled, CC Switch injects the following into the official live `config.toml`:
|
||||
|
||||
```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.
|
||||
|
||||
### 3. The two refusal gates for injection (corresponding to scenario C)
|
||||
|
||||
- `config.toml` already has an explicit `model_provider` -> don't override the user's route;
|
||||
- A differently-shaped `[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."
|
||||
|
||||
### 4. The three session classes (which determine the migration / restore boundary)
|
||||
|
||||
- **Class A**: existing official sessions migrated in at enable time—the backup is the ledger, and they can be precisely restored back to `openai`;
|
||||
- **Class B**: created during the unified period—in no backup, and official / third-party can't be distinguished, so they're **never moved automatically** (stay `custom`);
|
||||
- **Class C**: pure third-party history from before enabling—never touched.
|
||||
|
||||
### 5. The safety of migration / restore (data is never truly deleted; where the guarantee comes from)
|
||||
|
||||
Four layers of design jointly guarantee that under **all paths, normal and abnormal**, the original session data is never truly deleted.
|
||||
|
||||
- **Only change the field, never the body**: migration / restore only switch the `model_provider` value in session metadata between `openai` and `custom`; conversation content, `response_item`, and `encrypted_content` are all kept exactly as is.
|
||||
- **Always copy a backup before a rewrite**: jsonl uses file copy, the state DB uses a full SQLite copy, both stored in a timestamped generation directory. Migration backups live in `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.
|
||||
- **Only move, never delete + atomic writes**: all jsonl rewrites go through "temp file + whole-file replacement," and the state DB goes through a transactional `UPDATE`, with no deletion of any session or index at any point. The file is complete at every moment.
|
||||
- **Pessimistic skip + idempotent and retryable**: when buckets are inconsistent (`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.
|
||||
|
||||
### 6. Cross-backend encrypted_content (corresponding to scenario B)
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Keep Codex Remote Control and Official Plugins While Using Third-Party APIs: CC Switch Setup Guide](./codex-official-auth-preservation-guide-en.md)
|
||||
- [Using DeepSeek-Style Chat APIs in Codex: CC Switch Local Routing Guide](./codex-deepseek-routing-guide-en.md)
|
||||
- The "Codex App Enhancements" section in the CC Switch user manual
|
||||
|
||||
---
|
||||
|
||||
**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.**
|
||||
@@ -0,0 +1,467 @@
|
||||
# Codex セッション履歴の統一: 機能紹介と利用ガイド(CC Switch)
|
||||
|
||||
> 対象バージョン: CC Switch v3.16.x 以降。本記事は現在のコードをもとに整理しており、コマンドとパスはご自身で検証できます。例示には匿名化したデータを使用しており、実際のセッション内容や API Key は含まれていません。
|
||||
|
||||
## この機能とは何か
|
||||
|
||||
「Codex セッション履歴を統一」は、CC Switch v3.16.x が Codex 向けに新しく追加したスイッチです。その場所は **設定 → 一般 → 「Codex アプリ拡張」グループ** の中にあります(「Codex アプリ拡張」はこのグループの見出しで、スイッチ自体は「Codex セッション履歴を統一」という名前です)。オンにすると、**公式サブスクリプション(ChatGPT ログイン / OpenAI API Key)のセッションが、CC Switch で管理するすべてのサードパーティプロバイダーのセッションと同じ履歴 / セッション再開リストに表示されます**——もう、互いに見えない 2 つのリストに分断されることはありません。
|
||||
|
||||
## どんな問題を解決するのか
|
||||
|
||||
Codex は「プロバイダーのラベル」(`model_provider` というフィールド)でセッションを分類しており、しかも **セッション再開 / 履歴リストには、現在アクティブなプロバイダーと同じラベルのセッションしか表示しません**。そのため、セッションは自然と 2 つの「引き出し」に分けられてしまいます。
|
||||
|
||||
- 公式サブスクリプションのセッションは、Codex 内蔵の **`openai`** ラベルに分類されます。
|
||||
- CC Switch が管理するすべてのサードパーティプロバイダーは、**`custom`** ラベルに分類されます。
|
||||
|
||||
2 つの引き出しは互いに見えません。**公式とサードパーティを頻繁に切り替えている** 場合、この分断に遭遇します——「さっき公式で話したセッションが、サードパーティに切り替えたら履歴リストから消えた」というように。実際にはなくなっておらず、別の引き出しに分けられただけです。この分断は、セッションが失われたと誤解させやすいうえに、すべてのセッションを 1 か所でまとめて振り返ったり再開したりするのにも不便です。
|
||||
|
||||
**このスイッチは、まさにこの分断を解消するためのものです**。公式サブスクリプションも `custom` ラベルで動作させることで、公式とサードパーティのセッションが同じリストに統合され、探すのも再開するのも 1 か所で済みます。
|
||||
|
||||
> ✅ **本記事全体を貫く重要な前提を、まず覚えておいてください**: この機能(統一 / 移行 / 復元)は **常にセッション記録内のあの分類ラベル `model_provider` 1 つだけを書き換え、しかも毎回書き換える前に自動で元ファイルをバックアップします**。あなたの会話を 1 文たりとも削除・消去・上書きすることはありません。ですので、本記事の後半で「あるセッションが見えなくなった」とあっても、そのほとんどは「別の引き出しに分けられた」だけであり、「データが消えた」わけではありません——本当に心配なときは、[症状対照表](#会話が消えた症状対照表) と [自分の目でファイルが残っていることを確認する](#自分の目で確認-セッションファイルはディスク上に残っている最重要セクション) を直接ご覧ください。
|
||||
|
||||
## 動作原理(一言版)
|
||||
|
||||
これを **2 つの引き出し + 自動バックアップ** と考えてください。
|
||||
|
||||
- デフォルトでは、公式セッションは `openai` の引き出しに、サードパーティのセッションは `custom` の引き出しにあり、互いに見えません。
|
||||
- スイッチは **公式も `custom` の引き出しを使うように** させ、2 つの引き出しを 1 つの共有リストに統合します。
|
||||
- **既存の公式の古いセッション** も一緒に共有の引き出しへ「移す」ことを選べます(この操作を **移行** と呼びます。任意で、能動的にチェックを入れる必要があります)。そして **いかなる移動の前にも、まずコピーをバックアップ** するので、プロセス全体が **可逆** です。
|
||||
- **認証はまったく影響を受けません**——公式サブスクリプションは引き続きあなたの ChatGPT ログインを使い、引き続き公式バックエンドを経由します。変わるのはセッションの分類ラベルだけです。
|
||||
|
||||
完全な仕組み(何が注入されるのか、なぜ可逆なのか、移行 / 復元がどうやってデータ消失を防ぐのか)は、後述の [コア・メンタルモデル](#コアメンタルモデル-2-つの引き出し--自動バックアップ) と巻末の [応用原理付録](#応用原理付録仕組みを本当に理解したい人向け) をご覧ください。
|
||||
|
||||
## 使い方(クイック)
|
||||
|
||||
1. **有効化**: 設定 → 一般 → Codex アプリ拡張 → 「Codex セッション履歴を統一」をオン → ダイアログで「既存の公式セッション履歴も移行する」にチェックを入れるか決める(**以前** の公式セッションも統一リストに合流させたいならチェックを入れる。今後だけ統一したいならチェックを入れない)→ 確定。詳しくは [有効化したとき何が起きるか](#有効化したとき何が起きるか-ステップ別解説) を参照。
|
||||
2. **無効化**: 同じスイッチをオフにする → ダイアログで「バックアップから正確に復元する」のチェックを保持(デフォルトでチェック済み)→ 確定すれば、移行した公式セッションを正確に公式リストへ戻せます。詳しくは [無効化したとき何が起きるか](#無効化したとき何が起きるか-ステップ別解説) を参照。
|
||||
3. **セッションが消えた気がする?** 慌てずに [症状対照表](#会話が消えた症状対照表) へ進んで症状から原因を特定し、[自分の目で確認](#自分の目で確認-セッションファイルはディスク上に残っている最重要セクション) セクションのコマンドで、ファイルがすべて残っていることを自分の目で確かめてください。
|
||||
|
||||
---
|
||||
|
||||
## コア・メンタルモデル: 2 つの引き出し + 自動バックアップ
|
||||
|
||||
この機能を理解するには、**引き出し** と **バックアップ** の 2 つだけ覚えれば十分です。
|
||||
|
||||
### 引き出し: Codex はどうやってセッションを分類するか
|
||||
|
||||
Codex セッションを 1 つ開くたびに、Codex はセッションファイルの先頭に `model_provider` というラベルを記録し、「このセッションはどのプロバイダーで話したか」を示します。Codex の **セッション再開 / 履歴リストは、現在アクティブなこのラベルで正確にフィルタリングされます**——「今あなたが使っているプロバイダー」と同じラベルのセッションだけが表示されます。
|
||||
|
||||
- 公式サブスクリプション(ChatGPT ログイン / OpenAI API Key)のセッションのラベルは、内蔵の **`openai`** です。
|
||||
- CC Switch が管理するすべてのサードパーティプロバイダーは、一律にラベル **`custom`** を使います。
|
||||
|
||||
そのためデフォルトでは、公式セッションとサードパーティセッションは生まれつき互いに見えません——2 つの異なる引き出しにあるからです。これは **Codex 自身の設計** であり、CC Switch が何かをなくしたわけではありません。
|
||||
|
||||
```text
|
||||
デフォルト状態(統一スイッチをオンにしていない):
|
||||
┌──────────────────────┐ ┌──────────────────────────────────┐
|
||||
│ openai の引き出し │ │ custom の引き出し │
|
||||
│ (公式セッション) │ │ (サードパーティのセッション) │
|
||||
└──────────────────────┘ └──────────────────────────────────┘
|
||||
▲ ▲
|
||||
公式のときは サードパーティのときは
|
||||
こちらだけ表示 こちらだけ表示
|
||||
|
||||
(2 つの引き出しは互いに見えない)
|
||||
```
|
||||
|
||||
**「Codex セッション履歴を統一」スイッチがすることは、公式サブスクリプションも `custom` ラベルで動作させ、2 つの引き出しを 1 つに統合することです**。その結果、公式セッションとサードパーティセッションが同じセッション再開リストに表示されます。注意してほしいのは、**認証は変わらない** ということです——あなたの公式サブスクリプションは引き続き ChatGPT ログインを使い、引き続き公式バックエンドを経由します。変わるのはセッションの「分類ラベル」が `openai` から `custom` になることだけです。
|
||||
|
||||
```text
|
||||
統一スイッチをオンにした後:
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ custom 共有引き出し │
|
||||
│ 公式セッション + サードパーティのセッション │
|
||||
│ (同じ履歴 / 再開リストに表示される) │
|
||||
└────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### バックアップ: ラベルを変更する前に必ずコピーを取る
|
||||
|
||||
「引き出しの統合」では、一部の公式セッションのラベルを `openai` から `custom` に変更する必要があります(この操作を **移行** と呼び、これは **任意で、あなたが能動的にチェックを入れる必要があります**)。そして **どの書き換えの前にも、CC Switch はまず元ファイルをそのままコピー** して、ここに保存します。
|
||||
|
||||
```text
|
||||
~/.cc-switch/backups/codex-official-history-unify-v1/<時間スタンプ>/
|
||||
```
|
||||
|
||||
このバックアップが、後の「バックアップから正確に復元する」ための唯一の拠り所です。これによってプロセス全体が **可逆** になります——いつでもスイッチをオフにして、移行した公式セッションを正確に `openai` の引き出しへ戻せます。
|
||||
|
||||
この 2 つの言葉——**引き出し**(セッションは分類が変わるだけ)、**バックアップ**(変更前に必ずコピー)——を覚えておけば、以降の内容はすべて簡単に理解できます。
|
||||
|
||||
---
|
||||
|
||||
## 有効化したとき何が起きるか: ステップ別解説
|
||||
|
||||
### Step 1: スイッチを見つける
|
||||
|
||||
```text
|
||||
設定 → 一般 → Codex アプリ拡張
|
||||
```
|
||||
|
||||
「Codex アプリ拡張」のセクションには 2 行のスイッチがあり、**2 行目**(青い履歴アイコン)が本ガイドの主役です。
|
||||
|
||||
> **Codex セッション履歴を統一**
|
||||
|
||||
その下には説明文があります(逐語)。
|
||||
|
||||
> オンにすると、公式サブスクリプションも共有の custom プロバイダー ID で動作し、公式とサードパーティのセッションが同じ履歴リストに表示されます。既存の公式セッションの移行も選択できます(移行前に自動バックアップ)。オフにする際はバックアップから復元できます。注意:プロバイダーをまたいで古いセッションを再開すると、encrypted_content の推論内容を相手のバックエンドが復号できず、再開に失敗する場合があります。
|
||||
|
||||
> **注意**: この説明文には、すでに 3 つのことが予告されています——同じリストに表示される、移行を選べて自動バックアップされる、プロバイダーをまたいだ再開は「失敗する場合がある」。ここでの「再開に失敗する」は **続けられない、新しいターンを生成できない** という意味であり、「記録が消える」ではありません。これこそ、この後で重点的に解きほぐす核心的な誤解です。
|
||||
|
||||
### Step 2: スイッチをオフからオンに切り替える → 確認ダイアログが表示される
|
||||
|
||||
スイッチをオンに切り替えると、CC Switch は **すぐには保存せず**、まず確認ダイアログを表示します。ダイアログの文言は次のとおりです(逐語)。
|
||||
|
||||
- **タイトル**: Codex セッション履歴を統一
|
||||
- **本文**:
|
||||
|
||||
> オンにすると、公式サブスクリプションとサードパーティが同じセッション履歴リストを共有します。注意:プロバイダーをまたいで古いセッションを再開すると、encrypted_content を相手のバックエンドが復号できず失敗する場合があります。
|
||||
>
|
||||
> 既存の公式セッション履歴を共有リストへ移行することもできます(移行前に ~/.cc-switch/backups へ自動バックアップされ、オフにする際に復元を選択できます)。
|
||||
|
||||
- **チェックボックス**: 既存の公式セッション履歴も移行する
|
||||
- **確認ボタン**: 理解しました、オンにする
|
||||
- **キャンセルボタン**: キャンセル
|
||||
|
||||
**このチェックボックスはデフォルトでオフです。** これは重要な分岐点です。
|
||||
|
||||
| あなたの選択 | 効果 | この時点でデータはどこにあるか |
|
||||
|---|---|---|
|
||||
| **チェックしない**(デフォルト) | ラベルを切り替えるだけ。**オンにした後に新規作成された公式セッションだけ** が `custom` の共有引き出しに入る | あなたが **オンにする前** の公式の古いセッションは、ラベルが `openai` のまま、その場で動かず、引き続き `~/.codex/sessions/` にある |
|
||||
| **チェックする** | ラベルの切り替えに加えて、**既存の公式の古いセッション** も `openai` の引き出しから `custom` の引き出しへ移行する | 古いセッションは **コピーしてバックアップ** された後、ラベルが `custom` に書き換えられる。元データはバックアップで保護される |
|
||||
|
||||
> **「以前の公式セッションも統一リストに表示したい」なら、必ずこのチェックボックスを能動的にオンにしてください。** さもないと、下の対照表の「シナリオ A」に遭遇します——古いセッションが「消えた」ように見えますが、実際は元の引き出しに残っているだけです。
|
||||
|
||||
「キャンセル」を押すか、ダイアログの外側をクリックすると、スイッチはそのままオフ状態に戻り、何も起きません。
|
||||
「理解しました、オンにする」を押すと、スイッチはオンとして保存され、CC Switch はバックグラウンドで設定をディスクに書き込みます(移行にチェックを入れていれば、移行を実行します)。
|
||||
|
||||
### Step 3(移行にチェックを入れた場合のみ): 移行はどう実行されるか + データの安全性
|
||||
|
||||
「既存の公式セッション履歴も移行する」にチェックを入れた場合、CC Switch はあなたの公式の古いセッションに対して、次の一連の流れを実行します。
|
||||
|
||||
```text
|
||||
公式(openai ラベル)の各セッションファイルについて:
|
||||
① まず元ファイルをそのままバックアップディレクトリへコピー ← データの一次保険ができる
|
||||
② 「一時ファイルに書く → まるごと置換」という原子的な方法で、
|
||||
先頭行 session_meta 内の model_provider を
|
||||
"openai" から "custom" へ変更するだけ ← 会話本文は 1 バイトも触らない
|
||||
③ インデックス DB state_5.sqlite も同じトランザクション内でラベルを変更
|
||||
```
|
||||
|
||||
- **バックアップの場所**: `~/.cc-switch/backups/codex-official-history-unify-v1/<時間スタンプ>/`。移行のたびに、タイムスタンプ付きの「世代ディレクトリ」を生成し、その中に `jsonl/`(セッションのコピー)、`state/`(インデックス DB のコピー)、`meta.json`(この移行がどの Codex ディレクトリに属するかの記録)が含まれます。
|
||||
- **変更するもの**: `model_provider` というフィールドの値だけ。あなたの会話内容、推論内容、すべての本文は **そのまま保持** されます。
|
||||
- **削除するもの**: **何も削除しません**。バックアップは「コピー」、書き換えは「同一ファイルの原子的な置換」であり、全工程でセッションやインデックスを削除する操作は一切ありません。ファイルはいかなる時点でも完全です(古い内容か新しい内容かのどちらかであり、空や中途半端になることは決してありません)。
|
||||
|
||||
移行が成功すると、これらの公式の古いセッションが統一リストに表示されます。**この時点でのあなたのデータ**: ① 元のコピーがバックアップディレクトリにある。② アクティブファイルは分類ラベルが変わっただけで、内容は無傷。
|
||||
|
||||
> **注意**: 有効化と移行そのものは **成功通知を表示しません**。移行は保存時にバックエンドが付随的に実行するもので、UI 上ではスイッチがオン状態になったのが見えるだけです。ですので「移行成功のダイアログが見えなかった」のは正常であり、失敗を意味しません。
|
||||
|
||||
---
|
||||
|
||||
## 無効化したとき何が起きるか: ステップ別解説
|
||||
|
||||
### Step 1: スイッチをオンからオフに切り替える → バックアップを探索 → 確認ダイアログが表示される
|
||||
|
||||
無効化のとき、CC Switch はまず **一瞬かけて移行バックアップの有無を探索** し、それから確認ダイアログを表示します(そのため無効化のダイアログは少しだけ遅延しますが、これは正常です)。文言は次のとおりです(逐語)。
|
||||
|
||||
- **タイトル**: セッション履歴の統一をオフにする
|
||||
- **本文**:
|
||||
|
||||
> オフにすると、公式サブスクリプションとサードパーティはそれぞれ独立した履歴リストに戻ります。オン期間中に作成されたセッションは提供元を判別できないため、サードパーティの履歴に残り、公式サブスクリプションからは見えなくなります。
|
||||
|
||||
- **チェックボックス**(条件付き表示): オンにした際に移行した公式セッションを公式履歴へ復元する(バックアップから正確に復元)
|
||||
- **確認ボタン**: オフにする
|
||||
- **キャンセルボタン**: キャンセル
|
||||
|
||||
> **ポイント**: 本文が言っているのは「公式サブスクリプションからは **見えなくなる**」——**見えなくなる** であり、**削除される** ではありません。オン期間中に新たに話したセッションは、引き続き `custom` の引き出しに完全な形で残っており、オフにした後で公式側から見えなくなるだけです。
|
||||
|
||||
**この復元チェックボックスはデフォルトでオンです。** つまりデフォルトの動作は「オフにすると同時に、移行した公式セッションを公式履歴へ正確に復元する」です。チェックを保持したまま「オフにする」を押すだけで構いません。
|
||||
|
||||
チェックボックスが **表示されない** 場合は、復元が必要なバックアップがないとシステムが判断したことを意味します(移行に一度もチェックを入れていない、またはバックアップを探索できない)——この場合、あなたの公式の古いセッションは一度も変更されていないので、スイッチをオフにすれば自然と `openai` の引き出しに戻ります。
|
||||
|
||||
### Step 2: 復元はどう実行されるか(バックアップ台帳に従って正確に戻す)
|
||||
|
||||
チェックを保持して「オフにする」を押すと、CC Switch の復元フローは次のようになります。
|
||||
|
||||
```text
|
||||
① まず現在の状態を独立した復元バックアップディレクトリへもう一度コピー
|
||||
~/.cc-switch/backups/codex-official-history-unify-restore-v1/<時間スタンプ>/
|
||||
(復元自体もまずバックアップするので、復元でもデータは失われない)
|
||||
② すべての移行バックアップ世代を走査し、「当初のラベルが openai」のセッション id を集めて「台帳」を作る
|
||||
③ 【台帳に含まれ、かつ現在もまだ custom】のセッションだけ、ラベルを "openai" に戻す
|
||||
```
|
||||
|
||||
③ のステップの **二重条件** に注意してください——台帳に含まれていること(当初確かに公式から移行されたものだと証明できる)に加えて、現在もまだ `custom` であること(あなたが手動で変更していないことを示す)。両方の条件を満たして初めて戻します。これにより、復元は正確であり、かつ誤って手を加えることもありません。
|
||||
|
||||
**この時点でのあなたのデータ**: 戻された公式セッションはラベルが `openai` に変わり、再び公式リストに表示されます。同時に、移行バックアップと復元バックアップの 2 つのコピーがどちらもディスク上に残っています。
|
||||
|
||||
### Step 3: 通知を見て、結果を確認する
|
||||
|
||||
「オフにする + 復元にチェック」というパスだけが結果通知を表示します。表示され得る通知(逐語)。
|
||||
|
||||
| 表示される通知 | 意味 |
|
||||
|---|---|
|
||||
| **バックアップから公式セッション履歴を復元しました(セッションファイル {{files}} 件、インデックス {{rows}} 行)** | 復元成功。`{{files}}` / `{{rows}}` の部分には実際の数字が表示される |
|
||||
| **現在の Codex ディレクトリに復元可能な移行バックアップはありません** | 復元できる内容がない(**データが消えたわけではない**。対照表シナリオ E を参照) |
|
||||
| **統一セッション履歴が再度有効化されたため、復元をスキップしました** | 復元のキュー待ち中にスイッチを再びオンにしたため、システムが復元を自発的に取りやめた(対照表シナリオ F を参照) |
|
||||
| **公式セッション履歴の復元に失敗しました。もう一度お試しください** | 復元の途中でエラー。もう一度試せばよく、データは破壊されていない |
|
||||
| **保存に失敗しました。もう一度お試しください** | オフにするステップの保存そのものが失敗。この場合 **復元は決して起動されず**、スイッチは元の位置に戻る |
|
||||
|
||||
> **気の利いた安全設計**: 「スイッチをオフにする」ステップの保存が失敗した場合、CC Switch は **復元を決して実行しません**。さもないと「スイッチはまだオン、しかしセッションは `openai` バケットに戻された」という矛盾状態が生じてしまいます。保存失敗時、スイッチは **自動で元の位置に戻る** ので、「オフに見えるのに実は保存されていない」という偽の状態に取り残されることはありません。
|
||||
|
||||
---
|
||||
|
||||
## 「会話が消えた?」症状対照表
|
||||
|
||||
以下の 6 つのシナリオは、ユーザーが最も「セッションが消えた」と誤解しやすいケースです。**どれも真相は: データは無傷で、引き出しが変わったか一時的に見えないだけ。** まずこの表で症状から原因を特定し、その後で下の詳細説明を読んでください。
|
||||
|
||||
| シナリオ | あなたが見るもの | データの真相 | 一言での解決法 |
|
||||
|---|---|---|---|
|
||||
| **A** 移行にチェックなし | 公式の古いセッションが統一リストにない | すべて存在、`openai` ラベルのまま | 移行にチェックを入れて再度オンにする、またはスイッチをオフにする |
|
||||
| **B** プロバイダーをまたいだ再開が失敗 | 続けられない / エラー | ファイルは無傷、暗号文がバックエンドをまたいで復号できないだけ | 元のプロバイダーで再開する。内容だけ見るなら jsonl を直接読む |
|
||||
| **C** プロキシ接管 / 注入が拒否 | 移行も復元もされない | 移行が安全にスキップされ、ファイルは未変更 | 接管を終了 → 再起動して再試行。またはスイッチを直接オフにする |
|
||||
| **D** 復元後、新セッションが公式に戻らない | オン期間中の新セッションが公式にない | `custom` の引き出しにある、設計上動かさない | サードパーティプロバイダーに切り替えれば見える |
|
||||
| **E** 「復元可能なバックアップなし」と通知 | 復元が「失敗」 | 通常はそもそも移行していない、セッションは元の引き出しにある | スイッチをオフにすれば公式セッションが自動で再表示 |
|
||||
| **F** 「スイッチが再度有効化、復元スキップ」と通知 | 復元が拒否 | データの矛盾を防止、何も変更していない | まずスイッチを完全にオフにしてから復元する |
|
||||
|
||||
### シナリオ A: スイッチをオンにしたが移行にチェックを入れなかった → 公式の古いセッションが「消えた」
|
||||
|
||||
**現象**: 統一スイッチをオンにしたが、有効化ダイアログの「既存の公式セッション履歴も移行する」にチェックを入れなかった(デフォルトでチェックなし)。オンにした後で見ると、以前の公式の古いセッションがすべてリストにないように見える。
|
||||
|
||||
**真相**: データは 100% すべて存在し、1 行も動いていません。スイッチは「オンにした後に新規作成された」公式セッションにのみ効きます。あなたが **オンにする前** の公式の古いセッションはラベルが `openai` のままで、そっくりそのまま `~/.codex/sessions/` に横たわっています。今あなたがアクティブにしているのは `custom` の引き出しなので、`openai` の引き出しに残った古いセッションが見えないのは当然です——これが「消えたように見える」理由のすべてです。
|
||||
|
||||
**どうするか**(いずれか):
|
||||
1. **スイッチを再度オンにするときに「既存の公式セッション履歴も移行する」にチェックを入れ**、古いセッションを `custom` の引き出しへ移せば、すぐに統一リストに表示されます(書き換え前に自動バックアップ)。
|
||||
2. **または単に統一スイッチをオフにする** と、公式は再び `openai` の引き出しで動作し、古いセッションがその場で再表示されます。
|
||||
|
||||
### シナリオ B: プロバイダーをまたいで古いセッションを再開して失敗 → 「このセッションが壊れた / 消えた」と思う
|
||||
|
||||
**現象**: 統一した後、リストに「別のプロバイダー」で話した古いセッションが見える。今のプロバイダーに切り替えて「再開」を押すと、エラーになったり繋がらなかったりする。
|
||||
|
||||
**真相**: セッションファイルは完全に無傷で、失われたのはデータではなく「バックエンドをまたいだ復号能力」です。Codex セッションには暗号化された推論内容 `encrypted_content` が保存されており、**この暗号文は、それを生成したバックエンドだけが復号できます**。B プロバイダーで A プロバイダーが生成したセッションを再開しようとすると、B は A の暗号文を解けない → 再開失敗。これは **上流の Codex の設計上の制約(by design)** であり、CC Switch がファイルに手を加えたかどうかとは無関係です。セッション内の文字内容はいつでも読めます。
|
||||
|
||||
> これは本記事全体で **唯一「本当に問題が起きたように見える」実在の例外** です——ただし注意してください: これは **再開できない(新しいターンを生成できない)** だけであり、**元ファイルは依然として完全に存在し**、会話の文字はいつでも読めます。
|
||||
|
||||
**どうするか**:
|
||||
- **「このセッションを最初に作成したプロバイダー」で再開すれば**、正常に復号でき、繋がります。
|
||||
- 履歴の内容だけ見たくて、続ける必要がない場合は、そのセッションの `.jsonl` ファイルを直接読んでください(巻末にコマンドあり)。
|
||||
- 経験則: **プロバイダーをまたぐ場合は「新規セッションを始める」のが向いており、古いセッションはできるだけ元のプロバイダーで再開してください。**
|
||||
|
||||
### シナリオ C: スイッチをオンにし移行にもチェックを入れたが、移行が静かにスキップされた → 「移行がセッションをなくした」と思う
|
||||
|
||||
**現象**: オンにして移行にチェックを入れたのに、公式の古いセッションは統一リストに入らず、スイッチをオフにして復元しようとしても「復元できるものがない」と通知される(または無効化ダイアログに復元チェックボックスがそもそも現れない。シナリオ E を参照)。あなたは、移行の過程でセッションをなくしたのではと疑います。
|
||||
|
||||
**真相**: 移行はそもそも **実行されていない** ので、なくすことも不可能です——あなたのセッションは 1 文字も変更されていません。CC Switch には移行前に安全ゲートがあります: Codex の live 設定(`~/.codex/config.toml`)が、この時点で **本当に** 共有の `custom` 引き出しへルーティングされているかを確認し、本当にルーティングされている場合だけ移行します。以下の 2 つのケースでは「まだ統一されていない」と判定され(内部の理由コード `live_not_unified`)、**移行を自発的にスキップし、あなたのスイッチと移行の意思は保持し、条件が満たされてから移行します**。
|
||||
|
||||
- **プロキシ接管中**: CC Switch のプロキシが live 設定を接管しており、接管中の live には統一ルーティングのマークが付いていません。
|
||||
- **注入が拒否された**: あなたの `config.toml` にすでに手動指定の `model_provider` があるか、形態の異なる `[model_providers.custom]` テーブルが既に存在する(サードパーティのアドレスが付いている可能性がある)。公式トラフィックを誤ってサードパーティバックエンドへルーティングするのを避けるため、CC Switch は注入も移行もしないことを選びます。
|
||||
|
||||
移行のスキップ = どのセッションファイルにも触れない。**移行していない=動かしていない、消えようがない。** これは「安全な先送り」であり、「失敗してデータが消えた」ではありません。
|
||||
|
||||
**どうするか**:
|
||||
- プロキシ接管を終了 → **CC Switch を再起動**: 起動時に自動で移行を再試行します(あなたの移行の意思はずっと保持されています)。
|
||||
- `~/.codex/config.toml` を確認: 手動で書いた競合するルーティングがあれば、競合を整理してからスイッチをオンにします。
|
||||
- どうしても手間をかけたくない場合は、スイッチをオフにすれば、公式セッションは引き続き `openai` の引き出しで正常に表示され、まったく無傷です。
|
||||
|
||||
### シナリオ D: スイッチをオフにして復元したが、「オン期間中に新たに話したセッション」が公式に戻らない → 「新セッションが消えた」と思う
|
||||
|
||||
**現象**: 統一をオンにしている間、公式でさらにいくつかの新セッションを話した。後でスイッチをオフにし、復元にチェックを入れた。復元が終わると、その数本の新セッションが公式の引き出しに戻っていない。
|
||||
|
||||
**真相**: これは **意図的な** 設計で、新セッションはちゃんと `custom` の引き出しにあり、見えるし続けられます。復元の拠り所は「移行時のバックアップ台帳」です——**当初 `openai` の引き出しから移行されてきたセッションだけ** がバックアップに記録されており、正確に `openai` へ戻されます。あなたが **オン期間中に新規作成した** セッションはどのバックアップ台帳にもありません。しかも統一後は公式もサードパーティも `custom` ラベルを使うので、**CC Switch はこの新セッションが公式で話したものかサードパーティで話したものか判別できません**。サードパーティのセッションを公式履歴に誤って押し込まないため、プロダクトの決定として、これらの新セッションは一律に `custom`(サードパーティ)の履歴に残し、決して自動で動かしません。無効化ダイアログの文言もこれを明示しています——「オン期間中に作成されたセッションは提供元を判別できないため、サードパーティの履歴に残ります」。
|
||||
|
||||
**どうするか**:
|
||||
- 任意のサードパーティプロバイダー(`custom` の引き出し)に切り替えれば、履歴リストでこれらのセッションが見えます。
|
||||
- 内容を見たいなら `.jsonl` を直接読み、再開したいならシナリオ B のルール(それを生成した元のバックエンドに戻る)に従ってください。
|
||||
- もし **ある 1 本** を手動で公式に戻したい場合: 現在は自動ボタンはありません(方向を誤判定するのを避けるため、あえて作っていません)。上級ユーザーは、そのファイルを **先にバックアップ** したうえで、`.jsonl` の 1 行目 `session_meta` 内の `model_provider` を `custom` から `openai` に手動で戻せます(上級操作です。変更前に必ずコピーを取ってください)。
|
||||
|
||||
### シナリオ E: 復元時に「現在の Codex ディレクトリに復元可能な移行バックアップはありません」と通知 → 「復元失敗 = データが消えた」と思う
|
||||
|
||||
**現象**: スイッチをオフにするときに復元にチェックを入れたら、「現在の Codex ディレクトリに復元可能な移行バックアップはありません」と通知が出た。あなたは慌てます: 復元すら失敗した、データは完全に消えたのでは?
|
||||
|
||||
**真相**: 「復元できるものがない」≠「データが消えた」。むしろ逆で、通常は **そもそも復元すべき移行が存在しない** からです。よくある原因:
|
||||
|
||||
- **当初「既存の公式セッションを移行する」にチェックを入れていない**: 移行していない以上、移行バックアップもなく、戻すべきセッションもありません。あなたの公式の古いセッションはずっと `openai` の引き出しにあり、スイッチをオフにすれば直接再表示されます(シナリオ A と同じ)。(この場合、無効化ダイアログは復元チェックボックスを **そもそも表示しない** こともあります——システムがバックアップを一切探索できないためです。)
|
||||
- **すでに一度復元済み**: セッションラベルはすべて `openai` に戻っており、もう一度押しても「まだ `custom` の対象がない」のは当然です——これは **冪等保護であり、失敗ではありません**。
|
||||
- **Codex ディレクトリを切り替えた**: 復元は **現在の** ディレクトリに属するバックアップ台帳しか認識しないので、ディレクトリを変えると旧ディレクトリの台帳が見つかりません。ディレクトリを戻せば解決します。
|
||||
|
||||
この 3 つのケースでは、どのセッションも削除されていません。
|
||||
|
||||
**どうするか**: 巻末のコマンドで `~/.codex/sessions/` 内のセッションファイル総数を数え、ファイルがすべて残っていることを確認してください。次に `~/.cc-switch/backups/` に `codex-official-history-unify-v1` ディレクトリがあるかを見てください——もしこのディレクトリすらなければ、あなたは一度も移行を起動しておらず、セッションはずっと元の引き出しにある、ということです。
|
||||
|
||||
### シナリオ F: 復元が拒否され、「統一セッション履歴が再度有効化されたため、復元をスキップしました」と通知
|
||||
|
||||
**現象**: スイッチをオフにする → 復元にチェック → 手が速くて、すぐにスイッチを再びオンにした。すると「統一セッション履歴が再度有効化されたため、復元をスキップしました」と通知が出た。
|
||||
|
||||
**真相**: これはデータを「矛盾」状態にしてしまうのを防ぐ防護であり、セッションは同じく消えていません。復元の動作は「セッションラベルを `custom` から `openai` へ戻す」ことですが、この時点でスイッチが再びオンになっていると、live 設定は `custom` へルーティングしています——一方で履歴を `openai` へ戻し、一方で新セッションを `custom` に落とせば、セッションが人為的に 2 つに引き裂かれてしまいます。そのため CC Switch は「スイッチが再びオンになった」のを検知すると、**この復元を自発的に取りやめ、何も変更しません**。セッションは現状を維持し、削除も破壊もありません。
|
||||
|
||||
**どうするか**: 本当に復元したいなら、**まずスイッチを安定してオフにし**(すぐにオンにし直さない)、それから「オフにする + 復元にチェック」を実行してください。統一を保ちたいなら、復元せず、セッションを `custom` の共有引き出しに残して通常どおり使ってください。
|
||||
|
||||
**大原則: CC Switch の統一 / 移行 / 復元は、全工程でセッションの 1 つのラベルフィールドだけを変更し、しかも毎回書き換える前に自動でバックアップします。あなたの会話を削除することはありません。見えない ≠ 消えた——別の引き出しを見るか、下のコマンドで自分の目で確かめてください。**
|
||||
|
||||
---
|
||||
|
||||
## 自分の目で確認: セッションファイルはディスク上に残っている(最重要セクション)
|
||||
|
||||
文字をいくら重ねるより、自分の目で見るのが一番です。以下に **実際のパス**(CC Switch のソースコードから取得)と、異なる OS でセッションファイル・バックアップディレクトリを見る方法を示します。**全工程は読み取りのみで変更なし。ぜひ一度ご自身で試してみてください。**
|
||||
|
||||
### 最も簡単な方法: ファイルマネージャーで直接開く(コマンドライン完全不要)
|
||||
|
||||
- **macOS(Finder)**: `Cmd + Shift + G` を押して `~/.codex/sessions` を貼り付けて Enter すれば、たくさんの `.jsonl` セッションファイルとその更新時刻が見えます。バックアップディレクトリは `~/.cc-switch/backups` を貼り付けます。
|
||||
- **Windows(エクスプローラー)**: アドレスバーに `%USERPROFILE%\.codex\sessions` を貼り付けて Enter すれば、セッションフォルダとその中の `.jsonl` が見えます。バックアップディレクトリは `%USERPROFILE%\.cc-switch\backups` を貼り付けます。
|
||||
|
||||
**ここで一連の `.jsonl` ファイルが見えれば、それがセッションデータが無傷でディスク上にある証拠です。** ファイル数や更新時刻は、どんな文章よりも直感的です。
|
||||
|
||||
### あなたのセッション / 履歴ファイルはどこにあるのか
|
||||
|
||||
| 内容 | 実際のパス | 説明 |
|
||||
|---|---|---|
|
||||
| **セッション本文(コア)** | `~/.codex/sessions/`(日付別サブディレクトリを含む、再帰的) | セッション 1 つにつき 1 つの `.jsonl` テキストファイル。**これがあなたの会話内容** |
|
||||
| **アーカイブ済みセッション** | `~/.codex/archived_sessions/` | 同じく `.jsonl` |
|
||||
| **セッションインデックス DB** | `~/.codex/state_5.sqlite` | `threads` テーブルの `model_provider` 列が「引き出しラベル」。**これこそ、セッション再開リストが実際に読み取る分類のソース** |
|
||||
| **移行バックアップ**(移行をオンにすると自動生成) | `~/.cc-switch/backups/codex-official-history-unify-v1/<時間スタンプ>/` | `jsonl/`、`state/`、`meta.json` を含む |
|
||||
| **復元バックアップ**(復元を押すと自動生成) | `~/.cc-switch/backups/codex-official-history-unify-restore-v1/<時間スタンプ>/` | 復元前の安全なコピー |
|
||||
|
||||
> **注意**: CC Switch で Codex ディレクトリを変更した場合や、`config.toml` で `sqlite_home` を設定している場合は、上記の `~/.codex` をあなたの実際のディレクトリに置き換えてください。以下の `~` = あなたのユーザーホームディレクトリ。
|
||||
|
||||
### macOS / Linux コマンド
|
||||
|
||||
**1. セッションファイル総数を数える(これこそ「消えていない」確固たる証拠)**
|
||||
|
||||
```bash
|
||||
# セッションファイルの総数を数える —— この数が想定どおりなら、データはすべて残っている
|
||||
find ~/.codex/sessions ~/.codex/archived_sessions -name '*.jsonl' 2>/dev/null | wc -l
|
||||
|
||||
# 最近更新されたセッションファイル上位 10 件を見る
|
||||
find ~/.codex/sessions -name '*.jsonl' 2>/dev/null -print0 \
|
||||
| xargs -0 ls -lt 2>/dev/null | head -10
|
||||
```
|
||||
|
||||
**2. (補助)各「引き出し」にそれぞれ何個のセッションがあるか見る**
|
||||
|
||||
```bash
|
||||
# 公式の引き出し(openai)のセッションファイル数
|
||||
grep -rlE '"model_provider"[[:space:]]*:[[:space:]]*"openai"' ~/.codex/sessions 2>/dev/null | wc -l
|
||||
|
||||
# 統一の引き出し(custom)のセッションファイル数
|
||||
grep -rlE '"model_provider"[[:space:]]*:[[:space:]]*"custom"' ~/.codex/sessions 2>/dev/null | wc -l
|
||||
|
||||
# 各ラベルの分布をひと目で確認
|
||||
grep -rhoE '"model_provider"[[:space:]]*:[[:space:]]*"[^"]*"' ~/.codex/sessions 2>/dev/null | sort | uniq -c
|
||||
```
|
||||
|
||||
> **重要なヒント、このステップに驚かないでください**: **初期バージョンの Codex は `.jsonl` に `model_provider` フィールドを書き込みません**。これらの古い公式セッションは上記の grep では **数えられません** が、インデックス DB `state_5.sqlite` では依然として `openai` に分類されており、セッション再開リストではちゃんと見えます。ですので **「セッションが消えていない」かの判断はステップ 1 のファイル総数を基準にしてください**——バケット別 grep は分類を理解する補助に過ぎず、数えた結果がファイル総数より少ないのは **まったく正常** であり、決して「ひとまとまり消えた」ことを意味しません。
|
||||
|
||||
**3. (応用)インデックス DB `state_5.sqlite` を見る——セッション再開リストが実際に読む分類**
|
||||
|
||||
```bash
|
||||
# sqlite3 がインストール済みであること;未インストールならスキップ可
|
||||
sqlite3 ~/.codex/state_5.sqlite \
|
||||
"SELECT COALESCE(model_provider,'<空>'), COUNT(*) FROM threads GROUP BY 1;"
|
||||
```
|
||||
|
||||
> この `threads` テーブルこそ、Codex のセッション再開リストが実際に読み取る分類のソースであり、`openai` の行数 ≈ あなたの公式の引き出しで見えるセッション数です。ステップ 2 の jsonl grep とは数が合わないことがあります——その理由は、上述の「古いセッションは jsonl フィールドを書き込まないが、インデックス DB では依然として openai」だからです。両者が合わないのは異常ではありません。
|
||||
|
||||
**4. あるセッションの内容を直接読む(会話の文字が残っていることを確認)**
|
||||
|
||||
```bash
|
||||
# <ファイル名> を、上の ls で表示された .jsonl のパスに置き換える
|
||||
python3 -m json.tool < "<ファイル名>.jsonl" 2>/dev/null | head -50
|
||||
|
||||
# またはエディタで直接開いて見る(プレーンテキスト)
|
||||
open -e "<ファイル名>.jsonl" # macOS
|
||||
```
|
||||
|
||||
**5. CC Switch のバックアップディレクトリを見る(移行 / 復元の前に必ずコピーを残した証拠)**
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
### Windows コマンド(PowerShell)
|
||||
|
||||
セッションディレクトリは通常 `C:\Users\<あなたのユーザー名>\.codex\` にあり、バックアップは `C:\Users\<あなたのユーザー名>\.cc-switch\backups\` にあります。
|
||||
|
||||
```powershell
|
||||
# 1. セッションファイルの総数(「消えていない」ことの動かぬ証拠)
|
||||
(Get-ChildItem "$env:USERPROFILE\.codex\sessions","$env:USERPROFILE\.codex\archived_sessions" -Recurse -Filter *.jsonl -ErrorAction SilentlyContinue).Count
|
||||
|
||||
# 2. 最近更新されたセッション上位 10 件
|
||||
Get-ChildItem "$env:USERPROFILE\.codex\sessions" -Recurse -Filter *.jsonl |
|
||||
Sort-Object LastWriteTime -Descending | Select-Object -First 10 FullName,LastWriteTime
|
||||
|
||||
# 3. (補助)公式(openai) / 統一(custom) の引き出しにそれぞれ何件のセッションファイルがあるか
|
||||
(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. バックアップディレクトリを見る
|
||||
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
|
||||
```
|
||||
|
||||
> 同じく注意: ステップ 3 の grep の数がファイル総数より **少なくなる** のは正常です(古いセッションはこのフィールドを書き込まないため)。「セッションが消えていない」の判断は、ステップ 1 の **ファイル総数** を基準にしてください。
|
||||
|
||||
---
|
||||
|
||||
## 応用原理付録(仕組みを本当に理解したい人向け)
|
||||
|
||||
### 1. バケット分け機構(引き出しの本質)
|
||||
|
||||
Codex のセッション再開 / 履歴リストは、現在アクティブな `model_provider` id で **厳密な文字列フィルタリング** を行います。セッションファイル `.jsonl` の **1 行目** は `type:"session_meta"` のレコードで、その `payload.model_provider` がそのセッションの属する引き出しです(`grep -rl` はファイル内にそのラベルが 1 回でも出現すればそのファイルをカウントするので、行ごとに解析する必要はありません。旧バージョンでこのフィールドを書き込んでいないセッションは数えられません)。セッション再開リストを実際に駆動するのはインデックス DB `state_5.sqlite` の `threads.model_provider` 列です。公式サブスクリプションは `config.toml` に明示的な `model_provider` がないとき、内蔵のデフォルト id `openai` に入ります。CC Switch のすべてのサードパーティプロバイダーは一律に `custom` を使います。
|
||||
|
||||
### 2. スイッチがすること(注入、live にのみ存在)
|
||||
|
||||
オンにすると、CC Switch は公式 live `config.toml` に次の内容を注入します。
|
||||
|
||||
```toml
|
||||
model_provider = "custom"
|
||||
|
||||
[model_providers.custom]
|
||||
name = "OpenAI"
|
||||
requires_openai_auth = true
|
||||
supports_websockets = true
|
||||
wire_api = "responses"
|
||||
```
|
||||
|
||||
各フィールドには役割があります。`requires_openai_auth = true` は認証を引き続き `auth.json` 内の ChatGPT ログインで行わせ、base_url 未指定時は公式 Codex バックエンドへフォールバックさせます。`name = "OpenAI"` は Codex の公式機能ゲート(web search、リモート圧縮など)を引き続きヒットさせます。`supports_websockets = true` は custom エントリでデフォルトに失われる能力を補います。`wire_api = "responses"` は公式の responses プロトコルを使います。**正味の効果は: 認証は変わらず、バケット名が変わるだけ。**
|
||||
|
||||
**重要な不変条件: この注入は live `config.toml` にのみ存在でき、決してデータベースの保存設定には書き込まれません。** 公式プロバイダーから切り替えて離れ、live をデータベースへ書き戻すとき、CC Switch はこの注入を正確に剥離します(形態が注入物と完全に一致するときだけ剥離し、サードパーティがカスタムした `custom` テーブルはそのまま保持します)。だからこそ「スイッチをオフにする + 一度切り替える」だけで live を完全に復元でき、データベースには常にあなた本来のクリーンな公式設定が保たれます——これがスイッチ全体の可逆性の礎です。
|
||||
|
||||
### 3. 注入の 2 つの拒否ゲート(シナリオ C に対応)
|
||||
|
||||
- `config.toml` に明示的な `model_provider` がすでにある → ユーザーのルーティングを上書きしない。
|
||||
- 形態の異なる `[model_providers.custom]` テーブルがすでに存在する(サードパーティの `base_url` が付いている可能性がある)→ 注入を拒否、さもないと ChatGPT OAuth トラフィックを誤ったバックエンドへルーティングしてしまう。
|
||||
|
||||
注入を拒否したとき live は統一されず、移行ゲート(live の `model_provider` が trim 後に `custom` と等しいかを確認)が `live_not_unified` と判定 → 移行をスキップし、意思を保持し、次回起動の再試行時に行います。これは「安全な先送り」であり、「失敗してデータが消えた」ではありません。
|
||||
|
||||
### 4. セッションの三分類(移行 / 復元の境界を決める)
|
||||
|
||||
- **A 類**: オン時に移行した既存の公式セッション——バックアップが台帳であり、正確に `openai` へ復元可能。
|
||||
- **B 類**: オン期間中に新規作成——どのバックアップにもなく、公式 / サードパーティを判別不能、**決して自動で動かさない**(`custom` に残す)。
|
||||
- **C 類**: オン前の純粋なサードパーティ履歴——絶対に触れない。
|
||||
|
||||
### 5. 移行 / 復元の安全性(データが本当に削除されることはない、その保証はどこから来るか)
|
||||
|
||||
4 層の設計が共同で保証します: **正常・異常のあらゆるパス** において、元のセッションデータが本当に削除されることはありません。
|
||||
|
||||
- **フィールドだけ変更、本文には触れない**: 移行 / 復元はセッションメタデータ内の `model_provider` の値を `openai` と `custom` の間で切り替えるだけで、会話内容、`response_item`、`encrypted_content` はすべてそのまま保持します。
|
||||
- **書き換え前に必ずコピーをバックアップ**: jsonl はファイルコピー、state DB は SQLite の完全なコピーで、タイムスタンプ付きの世代ディレクトリに保存します。移行バックアップは `codex-official-history-unify-v1/` に、復元バックアップは独立した `codex-official-history-unify-restore-v1/` にあり、台帳を純粋に保つため両者は分けられています。
|
||||
- **移すだけ削除しない + 原子書き込み**: すべての jsonl 書き換えは「一時ファイル + 全体置換」を経由し、state DB はトランザクション化された `UPDATE` を経由し、全工程でセッションやインデックスを削除する操作は一切ありません。ファイルはいかなる時点でも完全です。
|
||||
- **悲観的スキップ + 冪等で再試行可能**: バケットが不一致のとき(`live_not_unified`)は移行しないことを選びます。一つのプロセスロックが移行と復元を直列化し、「起動時の再試行 / 保存後のバックグラウンドタスク / 無効化時の復元」が同じ一群のファイルを並行して双方向に書き換えるのを防ぎます。完了マークは Codex ディレクトリに紐づけて条件付きで書き込み、移行漏れを防ぎます。復元は「台帳にある + 現在もまだ custom」の二重条件を使い、誤変更を防ぎます。復元スキャンはすべてのバックアップ世代の和集合を取り、何度もスイッチを切り替えた後でも初期に移行したセッションを復元できます。重複した復元は `nothing_to_restore` を返しますが、これは冪等保護であり失敗ではありません。
|
||||
|
||||
### 6. バックエンドをまたいだ encrypted_content(シナリオ B に対応)
|
||||
|
||||
セッション内の推論暗号文は、それを生成したバックエンドだけが復号でき、上流の Codex は by design でバックエンドをまたいだ復号をサポートしません。これが「再開失敗」の根本原因であり、ファイルの完全性とは無関係です——セッション `.jsonl` は完全にディスク上に横たわり、`encrypted_content` も無傷です。元のプロバイダーに戻して再開するか、新規セッションを始めれば、どちらも正常です。
|
||||
|
||||
---
|
||||
|
||||
## 参考リンク
|
||||
|
||||
- [サードパーティ API 利用時に Codex のリモート操作と公式プラグインを保持する: CC Switch 設定ガイド](./codex-official-auth-preservation-guide-ja.md)
|
||||
- [Codex で DeepSeek などの Chat 形式 API を使う: CC Switch ローカルルーティングガイド](./codex-deepseek-routing-guide-ja.md)
|
||||
- CC Switch ユーザーマニュアル内の「Codex アプリ拡張」関連の章
|
||||
|
||||
---
|
||||
|
||||
**最後に一言**: あなたが見た「セッションが消えた / 再開失敗」は、本質的には **セッションが別の履歴リスト(引き出し)に移されたか、相手のバックエンドが古い推論内容を復号できない** ことであり、ファイルは常にそっくりそのまま `~/.codex/sessions/`(および `state_5.sqlite`)に横たわっています。スイッチをオフにするとき「バックアップから復元する」にチェックを入れれば、移行した公式セッションを正確に公式リストへ戻せます。たとえ復元しなくても、元の `.jsonl` ファイルと `~/.cc-switch/backups/codex-official-history-unify-*/` 配下のバックアップコピーはどちらも残っています——**データが本当に失われることは決してありません。**
|
||||
@@ -0,0 +1,467 @@
|
||||
# 统一 Codex 会话历史:功能介绍与使用攻略(CC Switch)
|
||||
|
||||
> 适用版本:CC Switch v3.16.x 及以上。本文根据当前代码整理,命令与路径均可亲手验证;示例使用去敏数据,不包含真实会话内容或 API Key。
|
||||
|
||||
## 这个功能是什么
|
||||
|
||||
「统一 Codex 会话历史」是 CC Switch v3.16.x 为 Codex 新增的一个开关。它的位置在 **设置 → 通用 → 「Codex 应用增强」分组**里("Codex 应用增强"是这个分组的标题,开关本身叫"统一 Codex 会话历史")。开启后,**官方订阅(ChatGPT 登录 / OpenAI API Key)的会话,会和 CC Switch 管理的所有第三方供应商会话,出现在同一个历史 / 续聊列表里**——不再被分隔在两个互相看不见的列表中。
|
||||
|
||||
## 它解决什么问题
|
||||
|
||||
Codex 自己按"供应商标签"(一个叫 `model_provider` 的字段)给会话分类,而且**续聊 / 历史列表只显示和你当前激活的供应商同标签的会话**。于是会话天然被分进两个"抽屉":
|
||||
|
||||
- 官方订阅的会话,归在 Codex 内建的 **`openai`** 标签下;
|
||||
- CC Switch 管理的所有第三方供应商,归在 **`custom`** 标签下。
|
||||
|
||||
两个抽屉互相看不见。如果你**经常在官方与第三方之间切换**,就会遇到这种割裂:"刚才用官方聊的会话,切到第三方后在历史列表里找不到了"——它其实没丢,只是被分到了另一个抽屉。这种割裂既容易让人误以为会话丢失,也不方便把所有会话放在一处统一回顾、续聊。
|
||||
|
||||
**这个开关就是为了消除这种割裂**:让官方订阅也以 `custom` 标签运行,于是官方与第三方会话合并进同一个列表,找起来、续起来都在一处。
|
||||
|
||||
> ✅ **一个贯穿全文的重要前提,请先记住**:这个功能(统一 / 迁移 / 还原)**全程只改写会话记录里那一个归类标签 `model_provider`,而且每次改写前都会自动把原文件备份一份**。它不会删除、清空或覆盖你的任何一句对话。所以本文后面若提到"某些会话看不到了",几乎都是"被分到了另一个抽屉",而不是"数据没了"——真担心时,直接看 [症状对照表](#我感觉会话丢了症状对照表) 与 [亲手验证文件还在](#亲手验证你的会话文件还在硬盘上最重要的一节)。
|
||||
|
||||
## 工作原理(一句话版)
|
||||
|
||||
把它想成 **两个抽屉 + 自动备份**:
|
||||
|
||||
- 默认时,官方会话在 `openai` 抽屉、第三方会话在 `custom` 抽屉,互不可见;
|
||||
- 开关让**官方也改用 `custom` 抽屉**,于是两个抽屉合并成一个共享列表;
|
||||
- 你可以选择把**现有的官方老会话**也一并"搬"进共享抽屉(这一步叫**迁移**,可选、需主动勾选),而**任何搬动前都会先复制一份备份**,所以整个过程**可逆**;
|
||||
- **认证完全不受影响**——官方订阅照常用你的 ChatGPT 登录、照常走官方后端,变的只是会话的归类标签。
|
||||
|
||||
完整机制(注入了什么、为什么可逆、迁移/还原如何保证不丢数据)见下文 [核心心智模型](#核心心智模型两个抽屉--自动备份) 与文末 [进阶原理附录](#进阶原理附录给想真正搞懂机制的用户)。
|
||||
|
||||
## 如何使用(速览)
|
||||
|
||||
1. **开启**:设置 → 通用 → Codex 应用增强 → 打开「统一 Codex 会话历史」→ 在弹窗里决定是否勾选"同时迁入现有官方会话历史"(想让**以前**的官方会话也并进统一列表,就勾上;只想从现在起统一,就不勾)→ 确认。详见 [开启时会发生什么](#开启时会发生什么分步说明)。
|
||||
2. **关闭**:关掉同一开关 → 弹窗里保持勾选"按备份精确还原"(默认就勾着)→ 确认,即可把当初迁入的官方会话精确翻回官方列表。详见 [关闭时会发生什么](#关闭时会发生什么分步说明)。
|
||||
3. **感觉会话丢了?** 别慌,跳到 [症状对照表](#我感觉会话丢了症状对照表) 按症状定位,并用 [亲手验证](#亲手验证你的会话文件还在硬盘上最重要的一节) 一节的命令亲眼确认文件都在。
|
||||
|
||||
---
|
||||
|
||||
## 核心心智模型:两个抽屉 + 自动备份
|
||||
|
||||
要理解这个功能,你只需要记住两件事:**抽屉**和**备份**。
|
||||
|
||||
### 抽屉:Codex 怎么给会话分类
|
||||
|
||||
你每开一个 Codex 会话,Codex 会在会话文件头部记一个标签 `model_provider`,标记"这条会话是用哪个供应商聊的"。Codex 的**续聊 / 历史列表是按当前激活的这个标签精确过滤的**——只显示和"你现在这个供应商"同标签的会话。
|
||||
|
||||
- 官方订阅(ChatGPT 登录 / OpenAI API Key)的会话,标签是内建的 **`openai`**。
|
||||
- CC Switch 管理的所有第三方供应商,统一用标签 **`custom`**。
|
||||
|
||||
所以默认情况下,官方会话和第三方会话天生互相看不见——它们在两个不同的抽屉里。这是 **Codex 自身的设计**,不是 CC Switch 弄丢了什么。
|
||||
|
||||
```text
|
||||
默认状态(没开统一开关):
|
||||
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ openai 抽屉 │ │ custom 抽屉 │
|
||||
│ (官方订阅会话) │ │ (第三方供应商会话)│
|
||||
└─────────────────┘ └─────────────────┘
|
||||
▲ ▲
|
||||
用官方时只看到这边 用第三方时只看到这边
|
||||
(两个抽屉互相看不见)
|
||||
```
|
||||
|
||||
**「统一 Codex 会话历史」开关做的事,就是让官方订阅也以 `custom` 标签运行,把两个抽屉合并成一个**,于是官方会话和第三方会话出现在同一个续聊列表里。注意:**认证没变**——你的官方订阅照常用你的 ChatGPT 登录、照常走官方后端,只是会话的"归类标签"从 `openai` 变成了 `custom`。
|
||||
|
||||
```text
|
||||
开启统一开关后:
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ custom 共享抽屉 │
|
||||
│ 官方订阅会话 + 第三方供应商会话 │
|
||||
│ (出现在同一个历史 / 续聊列表里) │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 备份:每次改标签前都先复制一份
|
||||
|
||||
"合并抽屉"需要把一部分官方会话的标签从 `openai` 改成 `custom`(这一步叫**迁移**,且是**可选的、需要你主动勾选**)。而**任何一次改写之前,CC Switch 都会先把原文件原封不动地复制一份**到这里:
|
||||
|
||||
```text
|
||||
~/.cc-switch/backups/codex-official-history-unify-v1/<时间戳>/
|
||||
```
|
||||
|
||||
这份备份,就是日后"按备份精确还原"的唯一依据。它让整个过程变得**可逆**:你随时可以关掉开关,把当初迁进来的官方会话精确地翻回 `openai` 抽屉。
|
||||
|
||||
记住这两个词——**抽屉**(会话只是换了归类)、**备份**(改前必先复制)——后面所有内容你都能轻松理解。
|
||||
|
||||
---
|
||||
|
||||
## 开启时会发生什么:分步说明
|
||||
|
||||
### 第 1 步:找到开关
|
||||
|
||||
```text
|
||||
设置 → 通用 → Codex 应用增强
|
||||
```
|
||||
|
||||
在"Codex 应用增强"这个区块里有两行开关,**第二行**(蓝色历史图标)就是本攻略的主角:
|
||||
|
||||
> **统一 Codex 会话历史**
|
||||
|
||||
它下方有一段说明文字(逐字):
|
||||
|
||||
> 开启后,官方订阅将以共享的 custom 供应商标识运行,官方与第三方会话出现在同一历史列表中,并可选择把现有官方会话一并迁入(迁移前自动备份)。关闭开关时可按备份恢复迁入的会话。注意:跨供应商继续旧会话时,对方后端可能无法解密会话中的 encrypted_content 推理内容,导致继续失败
|
||||
|
||||
> **注意**:这一句说明里已经预告了三件事——会出现在同一列表、可选迁入并自动备份、跨供应商续聊"可能继续失败"。这里的"继续失败"指的是**续不上、生成不了新回合**,不是"记录丢失"。这正是后面要重点拆解的核心误解。
|
||||
|
||||
### 第 2 步:把开关从关拨到开 → 弹出确认窗
|
||||
|
||||
一旦你把开关拨到开,CC Switch **不会立刻保存**,而是先弹出一个确认窗口。窗口文案如下(逐字):
|
||||
|
||||
- **标题**:统一 Codex 会话历史
|
||||
- **正文**:
|
||||
|
||||
> 开启后,官方订阅与第三方将共用同一个会话历史列表。注意:跨供应商继续旧会话时,可能因对方后端无法解密 encrypted_content 推理内容而失败。
|
||||
>
|
||||
> 可选择同时把现有官方会话历史迁入共享列表(迁移前自动备份到 ~/.cc-switch/backups,关闭开关时可选择恢复)。
|
||||
|
||||
- **复选框**:同时迁入现有官方会话历史
|
||||
- **确认按钮**:我已了解,继续开启
|
||||
- **取消按钮**:取消
|
||||
|
||||
**这个复选框默认是不勾选的。** 这是一个重要的分岔点:
|
||||
|
||||
| 你的选择 | 效果 | 此刻你的数据在哪 |
|
||||
|---|---|---|
|
||||
| **不勾**(默认) | 只切换标识。**只有开启之后新建的官方会话**才会落进 `custom` 共享抽屉 | 你**开启前**的官方老会话,标签仍是 `openai`,原地未动,仍在 `~/.codex/sessions/` |
|
||||
| **勾上** | 除了切换标识,还会把**现有的官方老会话**也从 `openai` 抽屉迁进 `custom` 抽屉 | 老会话被**复制备份**后,标签改写为 `custom`;原始数据有备份兜底 |
|
||||
|
||||
> **如果你希望"以前的官方会话也出现在统一列表里",必须主动勾选这个复选框。** 否则你会遇到下面对照表里的"场景 A"——老会话看起来"不见了",其实只是留在原抽屉里。
|
||||
|
||||
点"取消"或点窗口外面:开关直接弹回关闭状态,什么都没发生。
|
||||
点"我已了解,继续开启":开关保存为开启,CC Switch 在后台落盘配置(如果勾了迁移,就执行迁移)。
|
||||
|
||||
### 第 3 步(仅当勾了迁移):迁移如何执行 + 数据安全
|
||||
|
||||
如果你勾了"同时迁入现有官方会话历史",CC Switch 会对你的官方老会话做这套流程:
|
||||
|
||||
```text
|
||||
对每个官方(openai 标签)会话文件:
|
||||
① 先把原文件原样复制一份到备份目录 ← 数据有了第一道保险
|
||||
② 用「写临时文件 → 整体替换」的原子方式,
|
||||
只把头部那行 session_meta 里的 model_provider
|
||||
从 "openai" 改成 "custom" ← 对话正文一个字节都不动
|
||||
③ 索引数据库 state_5.sqlite 同步在一个事务里把标签改过来
|
||||
```
|
||||
|
||||
- **备份位置**:`~/.cc-switch/backups/codex-official-history-unify-v1/<时间戳>/`,每次迁移生成一个带时间戳的"代际目录",内含 `jsonl/`(会话副本)、`state/`(索引库副本)、`meta.json`(记录这次迁移属于哪个 Codex 目录)。
|
||||
- **改的是什么**:只有 `model_provider` 这一个字段值。你的对话内容、推理内容、所有正文**原样保留**。
|
||||
- **删的是什么**:**什么都没删**。备份是"复制",改写是"原子替换同一个文件",全程没有任何删除会话或索引的动作。文件在任何时刻都是完整的(要么是旧内容、要么是新内容,绝不会是空或半截)。
|
||||
|
||||
迁移成功后,这些官方老会话就出现在统一列表里了。**此刻你的数据**:① 原始副本在备份目录;② 活动文件里只有归类标签变了,内容完好。
|
||||
|
||||
> **注意**:开启与迁移本身**不会弹成功提示**。迁移是后端在保存时顺带跑的,UI 上你只会看到开关变成了打开状态。所以"没看到迁移成功的弹窗"是正常的,不代表失败。
|
||||
|
||||
---
|
||||
|
||||
## 关闭时会发生什么:分步说明
|
||||
|
||||
### 第 1 步:把开关从开拨到关 → 探测备份 → 弹出确认窗
|
||||
|
||||
关闭时,CC Switch 会**先花一瞬间探测有没有迁移备份**,然后弹出确认窗口(所以关闭弹窗会有一点点延迟,属正常)。文案如下(逐字):
|
||||
|
||||
- **标题**:关闭统一会话历史
|
||||
- **正文**:
|
||||
|
||||
> 关闭后,官方订阅与第三方将恢复各自独立的会话历史列表。开启期间产生的会话因无法区分来源,将留在第三方历史中,官方订阅将看不到它们。
|
||||
|
||||
- **复选框**(条件显示):把开启时迁入的官方会话还原回官方历史(按备份精确还原)
|
||||
- **确认按钮**:关闭
|
||||
- **取消按钮**:取消
|
||||
|
||||
> **划重点**:正文说的是"官方订阅**将看不到它们**"——是**看不到**,不是**删除**。开启期间你新聊的会话仍然完整地在 `custom` 抽屉里,只是关闭后官方那一侧看不到而已。
|
||||
|
||||
**这个还原复选框默认是勾选的。** 也就是说,默认行为就是"关闭的同时,把当初迁入的官方会话精确还原回官方历史"。你只要保持勾选、点"关闭"即可。
|
||||
|
||||
如果复选框**没有出现**,说明系统判断当前没有需要还原的备份(要么你从没勾过迁移、要么探测不到备份)——这种情况下你的官方老会话从没被改动过,关掉开关它们自己就回到 `openai` 抽屉了。
|
||||
|
||||
### 第 2 步:还原如何执行(按备份账本精确翻回)
|
||||
|
||||
如果你保持勾选并点"关闭",CC Switch 的还原流程是这样的:
|
||||
|
||||
```text
|
||||
① 先把当前现场再复制一份到独立的还原备份目录
|
||||
~/.cc-switch/backups/codex-official-history-unify-restore-v1/<时间戳>/
|
||||
(还原本身也先备份,所以还原也不会丢数据)
|
||||
② 翻遍所有迁移备份代际,找出"当初标签是 openai"的会话 id,组成一份"账本"
|
||||
③ 只对【既在账本里、当前又仍是 custom】的会话,把标签改回 "openai"
|
||||
```
|
||||
|
||||
注意第 ③ 步的**双重条件**——既要在账本里(证明它当初确实是官方迁来的),又要当前仍是 `custom`(说明你没手动改过它)。两个条件都满足才翻回。这保证了还原既精确又不会误伤。
|
||||
|
||||
**此刻你的数据**:被迁回的官方会话标签改回 `openai`,重新出现在官方列表;同时迁移备份和还原备份两份副本都还在硬盘上。
|
||||
|
||||
### 第 3 步:看提示,确认结果
|
||||
|
||||
只有"关闭 + 勾选还原"这条路径会弹结果提示。可能看到的提示(逐字):
|
||||
|
||||
| 你看到的提示 | 含义 |
|
||||
|---|---|
|
||||
| **已按备份还原官方会话历史({{files}} 个会话文件、{{rows}} 条索引记录)** | 还原成功。`{{files}}` / `{{rows}}` 处会显示实际数字 |
|
||||
| **当前 Codex 目录没有可恢复的迁移备份** | 没有可还原的内容(**不等于数据丢了**,详见对照表场景 E) |
|
||||
| **统一会话历史开关已重新开启,已跳过还原** | 还原排队期间你又把开关打开了,系统主动放弃还原(详见对照表场景 F) |
|
||||
| **还原官方会话历史失败,请重试** | 还原过程报错,重试即可,数据未被破坏 |
|
||||
| **保存失败,请重试** | 关闭这一步保存本身就失败了;此时**绝不会触发还原**,开关弹回原位 |
|
||||
|
||||
> **一个贴心的安全设计**:如果"关闭开关"这一步保存失败,CC Switch **绝不会去执行还原**。否则就会出现"开关还开着、会话却被翻回 openai 桶"的撕裂状态。保存失败时开关会**自动弹回原来的位置**,你不会停留在一个"看起来已关、实则没保存"的假状态里。
|
||||
|
||||
---
|
||||
|
||||
## "我感觉会话丢了?"症状对照表
|
||||
|
||||
下面六个场景,是用户最容易误以为"会话丢了"的情形。**每一个的真相都是:数据完好,只是换了抽屉或暂时看不到。** 先用这张表按症状定位,再看下面的详细说明。
|
||||
|
||||
| 场景 | 你看到的 | 数据真相 | 一句话解法 |
|
||||
|---|---|---|---|
|
||||
| **A** 没勾迁移 | 官方老会话不在统一列表 | 全在,仍带 `openai` 标签 | 重开并勾迁移,或关开关 |
|
||||
| **B** 跨供应商续聊失败 | 续不上 / 报错 | 文件完好,只是密文跨后端解不开 | 回原供应商续;只看内容直接读 jsonl |
|
||||
| **C** 代理接管 / 注入被拒 | 没迁也没还原 | 迁移被安全跳过,文件没动 | 退出接管 → 重启重试;或直接关开关 |
|
||||
| **D** 还原后新会话没回官方 | 开启期间新会话不在官方 | 在 `custom` 抽屉,设计上不动 | 切第三方供应商即可见 |
|
||||
| **E** 提示"没有可恢复备份" | 还原"失败" | 通常压根没迁移过,会话在原抽屉 | 关开关官方会话自动复现 |
|
||||
| **F** 提示"开关已重新开启,跳过还原" | 还原被拒 | 防数据撕裂,啥也没改 | 先彻底关开关再还原 |
|
||||
|
||||
### 场景 A:开了开关但没勾迁移 → 官方老会话"不见了"
|
||||
|
||||
**现象**:你开了统一开关,但开启弹窗里那个"同时迁入现有官方会话历史"没勾(它默认就不勾)。开启后一看,以前的官方老会话好像都不在列表里了。
|
||||
|
||||
**真相**:数据 100% 都在,一行都没动。开关只对"开启之后新建"的官方会话生效,你**开启前**的官方老会话标签仍是 `openai`,原封不动地躺在 `~/.codex/sessions/` 里。你现在激活的是 `custom` 抽屉,自然看不到留在 `openai` 抽屉里的老会话——这就是"看起来消失"的全部原因。
|
||||
|
||||
**怎么办**(任选其一):
|
||||
1. **重新开启开关时勾上"同时迁入现有官方会话历史"**,把老会话换到 `custom` 抽屉,它们立刻出现在统一列表(改写前自动备份)。
|
||||
2. **或者干脆关掉统一开关**,官方重新以 `openai` 抽屉运行,老会话原地复现。
|
||||
|
||||
### 场景 B:跨供应商续聊旧会话失败 → 以为"这条会话坏了 / 没了"
|
||||
|
||||
**现象**:统一之后列表里能看到一条用"另一家供应商"聊出来的旧会话,你切到现在的供应商点"继续",结果报错或接不上。
|
||||
|
||||
**真相**:会话文件完好无损,丢的不是数据,是"跨后端解密能力"。Codex 会话里保存了一段加密的推理内容 `encrypted_content`,**这段密文只有当初生成它的那个后端能解密**。你用 B 供应商去续 A 供应商生成的会话,B 解不开 A 的密文 → 续聊失败。这是**上游 Codex 的设计限制(by design)**,与 CC Switch 是否动过文件无关。会话里的文字内容你随时能读到。
|
||||
|
||||
> 这是整篇攻略里**唯一一个"看起来真出了问题"的真实例外**——但请注意:它只是**无法续聊(生成不了新回合)**,**原始文件依然完整存在**,对话文字随时可读。
|
||||
|
||||
**怎么办**:
|
||||
- **用"当初创建这条会话的那个供应商"去续聊**,就能正常解密、接上。
|
||||
- 只想看历史内容、不必继续?直接读那条会话的 `.jsonl` 文件(文末有命令)。
|
||||
- 经验法则:**跨供应商更适合"开新会话",老会话尽量回原供应商续。**
|
||||
|
||||
### 场景 C:开了开关也勾了迁移,但迁移被静默跳过 → 以为"迁移把会话弄丢了"
|
||||
|
||||
**现象**:你开启并勾了迁移,但官方老会话既没进统一列表、关开关想还原也提示没东西可还原(或者关闭弹窗里压根没出现还原复选框,参见场景 E)。你怀疑迁移过程中把会话搞丢了。
|
||||
|
||||
**真相**:迁移根本**没执行**,所以也不可能弄丢——你的会话一个字都没被改。CC Switch 在迁移前有一道安全闸门:它会检查 Codex 的 live 配置(`~/.codex/config.toml`)此刻是否**真的**路由到了共享 `custom` 抽屉,只有真路由过去了才迁移。以下两种情况会判定"还没统一"(内部原因码 `live_not_unified`),于是**主动跳过迁移、保留你的开关和迁移意愿、等条件满足后再迁**:
|
||||
|
||||
- **代理接管期间**:CC Switch 的代理接管了 live 配置,接管期的 live 不带统一路由标记。
|
||||
- **注入被拒**:你的 `config.toml` 已有手工指定的 `model_provider`,或已存在一张形态不同的 `[model_providers.custom]` 表(可能带第三方地址)。为避免把官方流量错误路由到第三方后端,CC Switch 宁可不注入、不迁移。
|
||||
|
||||
跳过迁移 = 不碰任何会话文件。**没迁,等于没动,谈不上丢。** 这是"安全延后",不是"失败丢数据"。
|
||||
|
||||
**怎么办**:
|
||||
- 退出代理接管 → **重启 CC Switch**:启动时会自动重试迁移(你的迁移意愿一直保留着)。
|
||||
- 检查 `~/.codex/config.toml`:若有你手工写的冲突路由,整理掉冲突后再开开关。
|
||||
- 实在不想折腾:直接关开关,官方会话仍以 `openai` 抽屉正常显示,毫发无损。
|
||||
|
||||
### 场景 D:关了开关并还原,但"开启期间新聊的会话"没回官方 → 以为"新会话丢了"
|
||||
|
||||
**现象**:你开启统一期间,用官方又聊了几条新会话。后来关开关、勾了还原,还原完发现那几条新会话没回到官方抽屉。
|
||||
|
||||
**真相**:这是**有意为之**的设计,新会话好端端在 `custom` 抽屉里,能看见、能续。还原的依据是"迁移时的备份账本"——**只有当初从 `openai` 抽屉迁进来的会话**,备份里有据可查,才会被精确翻回 `openai`。你**开启期间新建**的会话不在任何备份账本里;而且统一之后官方和第三方都用 `custom` 标签,**CC Switch 无法分辨这条新会话到底是官方聊的还是第三方聊的**。为了不把第三方会话误塞进官方历史,产品决策是:这些新会话一律留在 `custom`(第三方)历史里,绝不自动搬动。关闭弹窗的文案也明示了这一点——"开启期间产生的会话因无法区分来源,将留在第三方历史中"。
|
||||
|
||||
**怎么办**:
|
||||
- 切到任意一个第三方供应商(`custom` 抽屉),就能在历史列表里看到这些会话。
|
||||
- 想看内容直接读 `.jsonl`;想续聊遵循场景 B 的规则(回到当初生成它的后端)。
|
||||
- 如果你确实想把**某一条**手动归回官方:目前没有自动按钮(刻意不做,避免误判方向)。进阶用户可在**先备份**该文件后,手动把它 `.jsonl` 第一行 `session_meta` 里的 `model_provider` 从 `custom` 改回 `openai`(属高阶操作,改前务必复制一份)。
|
||||
|
||||
### 场景 E:还原提示"当前 Codex 目录没有可恢复的迁移备份" → 以为"还原失败 = 数据没了"
|
||||
|
||||
**现象**:关开关时勾了还原,结果弹出提示"当前 Codex 目录没有可恢复的迁移备份"。你慌了:还原都失败了,是不是数据彻底没了?
|
||||
|
||||
**真相**:"没有可还原的东西"≠"数据丢了"。恰恰相反,通常是因为**根本没有需要还原的迁移**。常见原因:
|
||||
|
||||
- **你当初没勾过"迁入现有官方会话"**:既然没迁移,自然没有迁移备份、也没有需要翻回去的会话。你的官方老会话一直在 `openai` 抽屉,关开关后直接复现(同场景 A)。(这种情况下,关闭弹窗甚至可能**根本不显示还原复选框**——因为系统探测不到任何备份。)
|
||||
- **已经还原过一遍了**:会话标签已全部翻回 `openai`,再点一次自然"没有仍是 custom 的目标可还原"——这是**幂等保护,不是失败**。
|
||||
- **切换过 Codex 目录**:还原只认属于**当前**目录的备份账本,换了目录就找不到旧目录的账本,把目录切回去即可。
|
||||
|
||||
这三种情况下,没有任何会话被删除。
|
||||
|
||||
**怎么办**:用文末命令统计 `~/.codex/sessions/` 里的会话文件总数,确认文件都在;再看 `~/.cc-switch/backups/` 里有没有 `codex-official-history-unify-v1` 目录——如果连这个目录都没有,说明你从没触发过迁移,会话一直在原抽屉。
|
||||
|
||||
### 场景 F:还原被拒,提示"统一会话历史开关已重新开启,已跳过还原"
|
||||
|
||||
**现象**:关开关 → 勾还原 → 你手很快,紧接着又把开关重新打开了,然后看到提示"统一会话历史开关已重新开启,已跳过还原"。
|
||||
|
||||
**真相**:这是一道防护,防止把数据弄成"撕裂"状态,会话同样没丢。还原的动作是"把会话标签从 `custom` 翻回 `openai`",但如果此刻开关又开着,live 配置正路由到 `custom`——一边把历史翻回 `openai`、一边新会话往 `custom` 落,会话会被人为撕成两半。所以 CC Switch 检测到"开关又开了",**主动放弃这次还原、什么都不改**。会话维持现状,没有任何删除或破坏。
|
||||
|
||||
**怎么办**:想真正还原,就**先把开关稳定地关掉**(别再立刻打开),再执行关闭 + 勾还原;想保持统一,就别还原,让会话留在 `custom` 共享抽屉正常使用。
|
||||
|
||||
**总原则:CC Switch 的统一 / 迁移 / 还原全程只改会话的一个标签字段,并且每次改写前都自动备份。它不会删你的对话。看不见 ≠ 丢了——换个抽屉看,或用下面的命令亲眼确认。**
|
||||
|
||||
---
|
||||
|
||||
## 亲手验证:你的会话文件还在硬盘上(最重要的一节)
|
||||
|
||||
文字再多,不如亲眼看见。下面给出**真实路径**(取自 CC Switch 源码)和在不同系统下查看会话文件、备份目录的方法。**全程只读不改,强烈建议你亲手试一遍。**
|
||||
|
||||
### 最简单的方式:用文件管理器直接打开(完全不用命令行)
|
||||
|
||||
- **macOS(Finder)**:按 `Cmd + Shift + G`,粘贴 `~/.codex/sessions` 回车,就能看到一堆 `.jsonl` 会话文件和它们的修改时间;备份目录粘贴 `~/.cc-switch/backups`。
|
||||
- **Windows(文件资源管理器)**:在地址栏粘贴 `%USERPROFILE%\.codex\sessions` 回车,就能看到会话文件夹和里面的 `.jsonl`;备份目录粘贴 `%USERPROFILE%\.cc-switch\backups`。
|
||||
|
||||
**只要你能在这里看到一批 `.jsonl` 文件,就证明会话数据完好无损地在硬盘上。** 文件数量、修改时间,比任何文字都直观。
|
||||
|
||||
### 你的会话 / 历史文件到底在哪
|
||||
|
||||
| 内容 | 真实路径 | 说明 |
|
||||
|---|---|---|
|
||||
| **会话正文(核心)** | `~/.codex/sessions/`(含按日期分的子目录,递归) | 每个会话一个 `.jsonl` 文本文件,**这就是你的对话内容** |
|
||||
| **归档会话** | `~/.codex/archived_sessions/` | 同为 `.jsonl` |
|
||||
| **会话索引数据库** | `~/.codex/state_5.sqlite` | `threads` 表的 `model_provider` 列就是"抽屉标签",**它才是续聊列表真正读取的归类来源** |
|
||||
| **迁移备份**(开启迁移时自动产生) | `~/.cc-switch/backups/codex-official-history-unify-v1/<时间戳>/` | 内含 `jsonl/`、`state/`、`meta.json` |
|
||||
| **还原备份**(点还原时自动产生) | `~/.cc-switch/backups/codex-official-history-unify-restore-v1/<时间戳>/` | 还原前的安全副本 |
|
||||
|
||||
> **注意**:如果你在 CC Switch 里改过 Codex 目录,或在 `config.toml` 里设了 `sqlite_home`,请把上面的 `~/.codex` 换成你的实际目录。下文 `~` = 你的用户主目录。
|
||||
|
||||
### macOS / Linux 命令
|
||||
|
||||
**1. 数会话文件总数(这才是"没丢"的硬证据)**
|
||||
|
||||
```bash
|
||||
# 统计会话文件总数 —— 只要这个数字符合你的预期,数据就都在
|
||||
find ~/.codex/sessions ~/.codex/archived_sessions -name '*.jsonl' 2>/dev/null | wc -l
|
||||
|
||||
# 看最近修改的 10 个会话文件
|
||||
find ~/.codex/sessions -name '*.jsonl' 2>/dev/null -print0 \
|
||||
| xargs -0 ls -lt 2>/dev/null | head -10
|
||||
```
|
||||
|
||||
**2. (辅助)看每个"抽屉"各有多少会话**
|
||||
|
||||
```bash
|
||||
# 官方抽屉(openai)会话文件数
|
||||
grep -rlE '"model_provider"[[:space:]]*:[[:space:]]*"openai"' ~/.codex/sessions 2>/dev/null | wc -l
|
||||
|
||||
# 统一抽屉(custom)会话文件数
|
||||
grep -rlE '"model_provider"[[:space:]]*:[[:space:]]*"custom"' ~/.codex/sessions 2>/dev/null | wc -l
|
||||
|
||||
# 看各标签分布一目了然
|
||||
grep -rhoE '"model_provider"[[:space:]]*:[[:space:]]*"[^"]*"' ~/.codex/sessions 2>/dev/null | sort | uniq -c
|
||||
```
|
||||
|
||||
> **重要提示,别被这一步吓到**:**早期版本的 Codex 不在 `.jsonl` 里写 `model_provider` 字段**,这些旧官方会话用上面的 grep 是**数不到**的,但它们在索引库 `state_5.sqlite` 里仍然归类为 `openai`、续聊列表照样能看到。所以**判断"会话没丢"请以第 1 步的文件总数为准**——分桶 grep 只是帮你理解归类,数出来比文件总数少**完全正常**,绝不代表"丢了一批"。
|
||||
|
||||
**3. (进阶)查索引库 `state_5.sqlite`——续聊列表真正读的归类**
|
||||
|
||||
```bash
|
||||
# 需要已安装 sqlite3;没装可跳过
|
||||
sqlite3 ~/.codex/state_5.sqlite \
|
||||
"SELECT COALESCE(model_provider,'<空>'), COUNT(*) FROM threads GROUP BY 1;"
|
||||
```
|
||||
|
||||
> 这张 `threads` 表才是 Codex 续聊列表真正读取的归类来源,`openai` 行数 ≈ 你官方抽屉里能看到的会话数。它和第 2 步的 jsonl grep 可能对不上数——原因就是上面说的"旧会话不写 jsonl 字段,但索引库里仍是 openai"。两边对不上不是异常。
|
||||
|
||||
**4. 直接读某条会话的内容(确认对话文字还在)**
|
||||
|
||||
```bash
|
||||
# 把 <文件名> 换成上面 ls 列出的某个 .jsonl 路径
|
||||
python3 -m json.tool < "<文件名>.jsonl" 2>/dev/null | head -50
|
||||
|
||||
# 或者直接用编辑器打开看(纯文本)
|
||||
open -e "<文件名>.jsonl" # macOS
|
||||
```
|
||||
|
||||
**5. 看 CC Switch 的备份目录(证明迁移 / 还原前都留了副本)**
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
### Windows 命令(PowerShell)
|
||||
|
||||
会话目录通常在 `C:\Users\<你的用户名>\.codex\`,备份在 `C:\Users\<你的用户名>\.cc-switch\backups\`。
|
||||
|
||||
```powershell
|
||||
# 1. 会话文件总数("没丢"的硬证据)
|
||||
(Get-ChildItem "$env:USERPROFILE\.codex\sessions","$env:USERPROFILE\.codex\archived_sessions" -Recurse -Filter *.jsonl -ErrorAction SilentlyContinue).Count
|
||||
|
||||
# 2. 最近修改的 10 个会话
|
||||
Get-ChildItem "$env:USERPROFILE\.codex\sessions" -Recurse -Filter *.jsonl |
|
||||
Sort-Object LastWriteTime -Descending | Select-Object -First 10 FullName,LastWriteTime
|
||||
|
||||
# 3. (辅助)官方(openai) / 统一(custom) 抽屉各多少会话文件
|
||||
(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. 看备份目录
|
||||
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
|
||||
```
|
||||
|
||||
> 同样提醒:第 3 步的 grep 数会**少于**文件总数属正常(旧会话不写该字段),请以第 1 步的**文件总数**作为"会话没丢"的判断依据。
|
||||
|
||||
---
|
||||
|
||||
## 进阶原理附录(给想真正搞懂机制的用户)
|
||||
|
||||
### 1. 分桶机制(抽屉的本质)
|
||||
|
||||
Codex 的续聊 / 历史列表按当前激活的 `model_provider` id **精确字符串过滤**。会话文件 `.jsonl` 的**第一行**是一条 `type:"session_meta"` 记录,其 `payload.model_provider` 即该会话所属抽屉(`grep -rl` 只要文件里出现一次该标签就计入该文件,因此无需逐行解析;旧版本未写该字段的会话则数不到)。真正驱动续聊列表的是索引库 `state_5.sqlite` 的 `threads.model_provider` 列。官方订阅在 `config.toml` 没有显式 `model_provider` 时落进内建默认 id `openai`;CC Switch 的所有第三方供应商统一用 `custom`。
|
||||
|
||||
### 2. 开关做的事(注入,只活在 live)
|
||||
|
||||
开启后,CC Switch 对官方 live `config.toml` 注入如下内容:
|
||||
|
||||
```toml
|
||||
model_provider = "custom"
|
||||
|
||||
[model_providers.custom]
|
||||
name = "OpenAI"
|
||||
requires_openai_auth = true
|
||||
supports_websockets = true
|
||||
wire_api = "responses"
|
||||
```
|
||||
|
||||
每个字段都有作用:`requires_openai_auth = true` 让认证继续走 `auth.json` 里的 ChatGPT 登录、base_url 缺省回落官方 Codex 后端;`name = "OpenAI"` 让 Codex 的官方特性门控(web search、远程压缩等)继续命中;`supports_websockets = true` 补回 custom 条目默认丢失的能力;`wire_api = "responses"` 用官方 responses 协议。**净效果是:认证没变,只是桶名变了。**
|
||||
|
||||
**关键不变量:这段注入只能存在于 live `config.toml`,绝不写进数据库的存储配置。** 切换离开官方供应商、把 live 回写数据库时,CC Switch 会把这段注入精确剥离(只在形态与注入产物完全一致时才剥,第三方自定义的 `custom` 表原样保留)。正因如此,"关掉开关 + 切换一次"就能彻底还原 live,数据库里始终是你原本干净的官方配置——这是整个开关可逆性的基石。
|
||||
|
||||
### 3. 注入的两道拒绝闸(对应场景 C)
|
||||
|
||||
- `config.toml` 已有显式 `model_provider` → 不覆盖用户路由;
|
||||
- 已存在形态不同的 `[model_providers.custom]` 表(可能带第三方 `base_url`)→ 拒绝注入,否则会把 ChatGPT OAuth 流量路由到错误后端。
|
||||
|
||||
拒绝注入时 live 不统一,迁移闸门(检查 live 的 `model_provider` 是否 trim 后等于 `custom`)判定 `live_not_unified` → 跳过迁移、保留意愿、等下次启动重试时再做。这是"安全延后",不是"失败丢数据"。
|
||||
|
||||
### 4. 会话三分类(决定迁移 / 还原边界)
|
||||
|
||||
- **A 类**:开启时迁入的存量官方会话——备份即账本,可精确还原回 `openai`;
|
||||
- **B 类**:开启期间新建——不在任何备份、官方 / 第三方不可分,**永不自动搬动**(留 `custom`);
|
||||
- **C 类**:开启前的纯第三方历史——绝不触碰。
|
||||
|
||||
### 5. 迁移 / 还原的安全性(数据不会被真正删除,保障来自哪里)
|
||||
|
||||
四层设计共同保证:在**正常与异常的所有路径**下,原始会话数据都不会被真正删除。
|
||||
|
||||
- **只改字段,不动正文**:迁移 / 还原只把会话元数据里的 `model_provider` 值在 `openai` 与 `custom` 之间切换,对话内容、`response_item`、`encrypted_content` 一律原样保留。
|
||||
- **改写前必先复制备份**:jsonl 用文件复制、state DB 用 SQLite 完整副本,存进时间戳代际目录。迁移备份在 `codex-official-history-unify-v1/`,还原备份在独立的 `codex-official-history-unify-restore-v1/`,两者分开以保持账本纯净。
|
||||
- **只移不删 + 原子写**:所有 jsonl 改写走"临时文件 + 整体替换",state DB 走事务化 `UPDATE`,全程没有任何删除会话或索引的动作。文件在任一时刻都是完整的。
|
||||
- **悲观跳过 + 幂等可重试**:桶不一致时(`live_not_unified`)宁可不迁;一把进程锁串行化迁移与还原,避免"启动重试 / 保存后台任务 / 关闭还原"并发对同批文件双向改写;完成标记按 Codex 目录绑定、条件写入,防漏迁;还原用"在账本 + 当前仍 custom"双重条件,防误改。还原扫描全部备份代际取并集,多次开关循环后仍能还原早期迁入的会话;重复还原返回 `nothing_to_restore`,是幂等保护而非失败。
|
||||
|
||||
### 6. 跨后端 encrypted_content(对应场景 B)
|
||||
|
||||
会话内的推理密文只能被生成它的后端解密,上游 Codex by design 不支持跨后端解密。这是"续聊失败"的根因,与文件完整性无关——会话 `.jsonl` 完整躺在磁盘上、`encrypted_content` 也完好无损。换回原供应商续聊,或开新会话,都正常。
|
||||
|
||||
---
|
||||
|
||||
## 参考链接
|
||||
|
||||
- [使用第三方 API 时保留 Codex 远程操作和官方插件:CC Switch 配置攻略](./codex-official-auth-preservation-guide-zh.md)
|
||||
- [在 Codex 中使用 DeepSeek 这类 Chat 格式 API:CC Switch 路由攻略](./codex-deepseek-routing-guide-zh.md)
|
||||
- CC Switch 用户手册中「Codex 应用增强」相关章节
|
||||
|
||||
---
|
||||
|
||||
**给你的最后一句话**:你看到的"会话不见了 / 续聊失败",本质是**会话被换到了另一个历史列表(抽屉)里、或对方后端无法解密旧推理内容**,文件始终原封不动地躺在 `~/.codex/sessions/`(及 `state_5.sqlite`)里。关闭开关时勾选"按备份还原"即可把当初迁入的官方会话精确翻回官方列表;即便不还原,原始 `.jsonl` 文件和 `~/.cc-switch/backups/codex-official-history-unify-*/` 下的备份副本也都在——**数据绝不会真正丢失。**
|
||||
Reference in New Issue
Block a user