# 统一 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-*/` 下的备份副本也都在——**数据绝不会真正丢失。**
