--- name: codex-model-catalog-history-loss description: 「Codex切模型后会话记录消失」的真因——config指向的model不在模型目录→Codex以未知模型启动、侧栏历史还原失败。排查此类"记录丢失"必读 metadata: type: project --- 客户报「Codex 切模型后会话记录莫名丢失」。真因不是数据丢失,是**显示丢失**:`~/.codex/config.toml` 的根级 `model` 指向了一个**不在模型目录 `cc-switch-model-catalog.json` 里的 slug** → Codex 本体以未知模型启动,无法解析该模型 → 侧栏历史线程还原失败,看起来"会话没了"。rollout 文件、`state_*.sqlite`(threads)、`session_index.jsonl` 全部无损,切回一个目录内已有的模型(或把该 model 登记进目录)后重启 Codex 即恢复。 **触发的代码 bug(electron/lib/router.js,已修)**:`applyModelCatalog(group)` 旧代码首行 `if (!group.contextWindow) return` + `if(!win) return`,即分组没填「上下文窗口」就跳过目录登记;而 `setConfigModel(model)` 却无条件把 `model=` 写进 config.toml → **非对称**:config 指向的模型没进目录。分组的 contextWindow 除 DeepSeek 外普遍为空,所以切到目录里没有的模型必现。 **修复**:① `applyModelCatalog` 去掉 contextWindow 前置条件,只要有 model 就登记(visibility=list,窗口回退 输入→模板默认→128000);② 新增 `ensureModelInCatalog()` 内嵌进 `setConfigModel`,写 config 前保证该 slug 在目录里 = 不变量「config 指向的 model 必在目录」,任何切换路径+历史残骸环境都自愈。③ **关键补强**:用户是在 Codex 本体的模型选择器里切模型的,那条路径**不经过本 App**,只登记单个 model 不够——切到别的同代模型(如 tokendun 有 gpt-5.6-luna/sol/terra 三个)又会丢。故新增 `syncCatalogFromProvider(group)`:拉分组 `/v1/models`(复用 `fetchModels`),把全部**文本**模型(排除 image/imagine/whisper/audio/tts/embed/rerank/review 等非对话模型,正则 NON_TEXT_MODEL_RE)一次性登记进目录。在 `applyDirectToCodex` / `applyToCodex`(fire-and-forget) 及 **main.js 应用启动时**(`router.llmGroup()` 取当前激活分组) 各触发一次 → 既有安装无需重存分组即自动修复。 **已发布**:热更 v1.5.20(mandatory=true, min 1.5.0)已上 gitea hot 通道。升级后**仍需用户手动重启 Codex 本体**(App 修好目录,Codex 下次启动才读)。发布见 [[release-publish-flow]]。 **诊断手法(会话本文不用读,只看分布)**: - `sqlite3 state_*.sqlite "SELECT model_provider,model,COUNT(*) FROM threads GROUP BY 1,2"` → 全 null/同值说明侧栏不是按 model/provider 过滤,排除 provider-sync 方向。 - node 读 catalog 打印各 `slug|visibility|context_window`,比对 config.toml 的 `model=` 是否在其中 —— 不在即命中本 bug。 - `session_index.jsonl.bak-*` 若有多份,是用户反复跑 provider-sync 抢救历史的痕迹。 单机即时抢救:把 config 当前 model 用模板克隆登记进 catalog(写 .bak-fix 备份),重启 Codex 即回。相关:[[router-dual-platform-config]] [[router-dual-active-slots]] [[release-publish-flow]]。