在 Codex Desktop / Codex CLI 中,如果在两种登录方式之间切换:
- OpenAI 账号登录
- 自定义 OpenAI-compatible API 登录,例如
https://cat.api.boji1334.com
切换完成后,原来的聊天会话可能会从侧边栏或会话列表中消失。
目前观察到的现象更像是“会话没有丢失,只是当前登录方式 / provider 下没有被索引或展示出来”。本地的 rollout 文件和 sqlite 索引仍然存在。
只记一句话:先切到你要用的登录模式,再同步会话,最后打开 Codex。
API 登录:
-
关闭 Codex,确保后台没有 Codex 进程。
-
按页面提示安装 CC Switch,输入 sub2api 地址和 API Key,一键导入并保存配置。
-
如果你的 sub2api 平台要求 provider 名为
sub2api,确认config.toml里是model_provider = "sub2api"。 -
运行:
cd path\to\codex-session-sync .\codex-unify-sessions.bat
-
打开 Codex。
账号登录:
-
打开 Codex,完成账号登录。
-
关闭 Codex。
-
运行:
cd path\to\codex-session-sync .\codex-unify-sessions.bat
-
再打开 Codex。
API 登录:
-
关闭 Codex,确保后台没有 Codex 进程。
-
按页面提示安装 CC Switch,输入 sub2api 地址和 API Key,一键导入并保存配置。
-
如果你的 sub2api 平台要求 provider 名为
sub2api,确认config.toml里是model_provider = "sub2api"。 -
运行:
cd /path/to/codex-session-sync sh ./codex-unify-sessions.sh -
打开 Codex。
账号登录:
-
打开 Codex,完成账号登录。
-
关闭 Codex。
-
运行:
cd /path/to/codex-session-sync sh ./codex-unify-sessions.sh -
再打开 Codex。
脚本支持 Windows、macOS、Linux。Windows 用 .bat,macOS/Linux 用 .sh,底层都是同一个 Python 合并逻辑。
同步脚本会自动读取当前 config.toml 的 model_provider。如果你的 sub2api 部署固定使用 sub2api,就保持 model_provider = "sub2api",不要切成别的 provider 名。
-
使用 OpenAI 账号登录 Codex,创建几个聊天会话。
-
退出或切换到自定义 API provider。
-
示例配置:
model_provider = "custom" [model_providers.custom] name = "custom" base_url = "https://cat.api.boji1334.com" wire_api = "responses" requires_openai_auth = true
-
重启 Codex 或刷新会话列表。
-
观察:之前 OpenAI 账号登录时创建的会话不再显示,或者只显示当前 provider 下创建的会话。
-
再切回 OpenAI 账号登录时,也可能出现另一批会话不可见。
本地聊天会话应该和具体模型 provider 解耦。
用户切换登录方式或 API base URL 后,仍然应该能看到同一个本地工作区下的历史会话。会话可以显示创建时使用的 provider,但不应该因为当前 provider 不同而从列表里消失。
会话似乎按当前登录方式、provider 或 account scope 被分桶展示。
结果是:
- 用户会误以为历史聊天被删除。
- 但本地文件仍然存在。
- 只有当前 provider / account 能索引到的会话会出现在 UI 里。
Codex 的本地会话通常在这些位置:
Windows:
$env:USERPROFILE\.codex\sessions
$env:USERPROFILE\.codex\archived_sessions
$env:USERPROFILE\.codex\state_5.sqlite
$env:USERPROFILE\.codex\session_index.jsonlmacOS / Linux:
~/.codex/sessions
~/.codex/archived_sessions
~/.codex/state_5.sqlite
~/.codex/session_index.jsonl其中:
sessions/保存未归档会话的 rollout JSONL。archived_sessions/保存归档后的 rollout JSONL。state_5.sqlite里的threads表保存会话索引。threads表包含model_provider、source、cwd、title、rollout_path、archived等字段。
本机检查时,threads.rollout_path 指向的 JSONL 文件仍然存在,说明会话正文没有丢失。
先备份整个 Codex 目录:
Copy-Item "$env:USERPROFILE\.codex" "$env:USERPROFILE\.codex.backup" -Recurse然后可以确认会话文件是否还在:
Get-ChildItem "$env:USERPROFILE\.codex\sessions" -Recurse -Filter "*.jsonl"
Get-ChildItem "$env:USERPROFILE\.codex\archived_sessions" -Recurse -Filter "*.jsonl"如果只是 UI 中看不到,会话通常还能从 JSONL 文件或 sqlite 索引中恢复。不要删除 .codex 目录,也不要覆盖 state_5.sqlite。
这个仓库提供了一键合并脚本。
Windows:
.\codex-unify-sessions.batmacOS / Linux:
sh ./codex-unify-sessions.sh所有系统通用的 Python 兜底命令:
python tools/merge_codex_sessions.py --apply如果系统里 Python 命令叫 python3:
python3 tools/merge_codex_sessions.py --apply它会自动读取当前 Codex 模式:
- 如果当前有
config.toml且里面写了model_provider,就把所有本地会话合并到这个 provider。 - 如果当前是账号登录模式,没有
config.toml,就按openai合并。
执行前建议先关闭 Codex,执行完重新打开 Codex。脚本只修改 state_5.sqlite 的会话索引,不修改 sessions/**/*.jsonl 或 archived_sessions/**/*.jsonl 聊天正文。
安全策略:
- 写入前会用 sqlite backup API 备份
state_5.sqlite。 - 备份会放在
~/.codex/session-sync-backups/<时间>/。 - 写入前会执行
PRAGMA quick_check。 - 写入前会检查
threads表结构是否匹配。 - 写入事务提交前会确认
threads数量没有减少。 - 写入事务提交前会确认扫描到的 rollout 会话都还在索引里。
- 如果任何一步失败,脚本会退出,不会强行写入。
如果合并后想恢复,关闭 Codex,把备份目录里的 state_5.sqlite 复制回 ~/.codex/state_5.sqlite 即可。聊天正文 JSONL 没有被改过,所以不会因为这个脚本丢失。
底层 Python 脚本也可以单独预览:
python .\tools\merge_codex_sessions.py默认是预览模式,不会修改任何文件。它会做三件事:
- 扫描
~/.codex/sessions和~/.codex/archived_sessions。 - 检查
state_5.sqlite里的threads索引有没有缺失或归档路径变化。 - 统计不同
model_provider下的会话数量,并计算需要合并多少条。
确认无误后执行:
python .\tools\merge_codex_sessions.py --apply执行时脚本会先备份 state_5.sqlite,然后:
- 补齐缺失的会话索引。
- 修复已归档会话的
rollout_path。 - 把所有本地会话的
model_provider改成当前配置里的 provider。
如果当前是账号登录,且没有 config.toml,脚本会按 openai 处理:
python .\tools\merge_codex_sessions.py --apply --provider openai如果当前是自定义 API 登录,可以指定 provider 名称:
python .\tools\merge_codex_sessions.py --apply --provider boji1334这样切换到账号登录时跑一次 --provider openai,切换到 API 登录时跑一次当前 API provider,例如 CC Switch 默认的 --provider boji1334,两边看到的就是同一批本地聊天记录。
如果你已经有自己的切换脚本,可以在切换完成后调用同步脚本:
- 账号登录后执行
.\codex-unify-sessions.bat --provider openai。 - API 登录后执行
.\codex-unify-sessions.bat,让它自动读取 CC Switch 写入的 provider。 - 如果要手动指定,CC Switch 默认是
.\codex-unify-sessions.bat --provider boji1334。
建议上游在会话列表和索引层做以下改动:
- 会话列表默认展示所有本地 provider 的会话,而不是只展示当前 provider。
- UI 中增加 provider 过滤器,例如
All、openai、custom api。 threads表可以保留model_provider字段作为元数据,但不应该把它作为默认隔离边界。- 切换登录方式后触发一次本地索引重建,从
sessions/和archived_sessions/扫描 JSONL,补齐state_5.sqlite.threads。 - 当当前 provider 与会话创建 provider 不一致时,允许用户打开历史会话,并在继续对话前提示“将使用当前 provider 继续”。
一个安全的修复流程可以是:
- 扫描
~/.codex/sessions/**/*.jsonl和~/.codex/archived_sessions/**/*.jsonl。 - 读取每个 JSONL 的第一条
session_meta。 - 提取
id、cwd、source、model_provider、timestamp、cli_version。 - 从后续消息中生成或保留
title、updated_at、has_user_event。 - 用
idupsert 到state_5.sqlite.threads。 - 不修改原始 rollout 文件。
Title:
Local sessions disappear from UI after switching between account login and custom API provider
Body:
### Summary
When switching Codex between OpenAI account login and a custom OpenAI-compatible API provider, existing local chat sessions disappear from the UI. The underlying rollout JSONL files still exist, so this appears to be a session indexing / filtering issue rather than data loss.
### Steps to reproduce
1. Login with an OpenAI account.
2. Create a few Codex conversations.
3. Switch to a custom API provider with a custom `base_url`.
4. Restart Codex or refresh the session list.
5. Observe that previous sessions are no longer visible.
### Expected behavior
Local sessions should remain visible across provider changes. The UI may show which provider created a session, but switching providers should not hide local history by default.
### Actual behavior
Only sessions associated with the current account / provider appear to be listed.
### Local investigation
The local Codex directory still contains rollout files under:
- `~/.codex/sessions`
- `~/.codex/archived_sessions`
The sqlite index at `~/.codex/state_5.sqlite` has a `threads` table with fields including:
- `id`
- `rollout_path`
- `model_provider`
- `cwd`
- `title`
- `archived`
The rollout paths still point to existing files, which suggests the sessions are not lost.
### Suggested fix
Please decouple local session visibility from the current provider by default. A provider filter would be useful, but the default should be `All local sessions`.
It would also help to add a safe local index rebuild operation that scans rollout JSONL files and repopulates missing `threads` rows.不要把下面这些文件直接发到 GitHub:
auth.jsonconfig.toml中的 token / keylogs_*.sqlitestate_*.sqlitesessions/**/*.jsonlarchived_sessions/**/*.jsonl
这些文件可能包含 API key、账号信息、聊天正文、项目路径或其他隐私数据。