问题描述 / What happened
自定义供应商已存的 API key 密文一旦用当前 safeStorage 密钥解不开(例如 Keychain 密钥变更、或密文由另一个构建写入),该供应商的配置更新会永久死锁:即使用户在表单里粘贴了全新的 API key 点保存,也始终报 failed to read existing <agent> provider credential,没有任何自救路径(UI 上无法清除坏密文,只能手动去 safe-storage/ 目录删 .enc 文件)。
根因链路:
期望行为(建议方向,供维护者裁量):
- 当本次 mutation 携带显式替换值时,快照解密失败可降级为「快照 = 不可恢复」,回滚语义退化为删除该 runtime 的密文,不阻塞覆盖写入;仅在 mutation 是「保留旧值」或「仅删除」语义时维持现有严格失败。
- 或者至少:错误信息告知用户旧凭证已损坏及恢复方式(重新填 key / 提供「清除已存凭证」入口),而不是笼统的 INTERNAL。
环境 / Environment
- Cindy 版本或 commit / version or commit: 0.1.71(release 安装版);main
11e02c03 上代码路径一致
- 平台与版本 / platform & OS version: macOS (Darwin 25.4.0, Apple Silicon)
- 安装方式 / install method: 官方 dmg 安装到 /Applications
复现步骤 / Steps to reproduce
- 自定义供应商配置好 API key(密文落盘为
userData/safe-storage/owner_<id>_provider_key_<provider>_<agent>.enc)。
- 使该密文对当前 safeStorage 密钥不可解。实机是历史写入的密文解不开(疑似曾由不同签名/构建写入);模拟复现可直接用任意 base64 垃圾字节覆盖该
.enc 文件内容。
- 打开「编辑自定义供应商」,粘贴一个全新的 API key,点保存。
- 观察:顶部 toast 报
failed to read existing claude-code provider credential,保存失败;反复重填 key 均无效,UI 无任何恢复路径。
- 手动把对应
.enc 文件移走后再保存 → 立即成功(证明死锁仅由坏密文快照引起)。
日志与截图 / Logs & screenshots
[2026-09-02T22:36:34.282+08:00] [WARN ] [providerSecretStore] {
providerId: 'kimi-code',
agent: 'claude-code',
err: 'Error while decrypting the ciphertext provided to safeStorage.decryptString.'
} decrypt custom provider key snapshot failed
[2026-09-02T22:36:34.282+08:00] [ERROR] [console] Error occurred in handler for 'maker:provider:custom:update': Error: [INTERNAL] failed to read existing claude-code provider credential
at createIpcError (.../bootstrap-electron-ClZHv728.js:221:19129)
at throwIpcError (.../bootstrap-electron-ClZHv728.js:221:19279)
(已按模板要求删除凭证与个人数据;kimi-code 为本机自定义供应商 id。)
问题描述 / What happened
自定义供应商已存的 API key 密文一旦用当前 safeStorage 密钥解不开(例如 Keychain 密钥变更、或密文由另一个构建写入),该供应商的配置更新会永久死锁:即使用户在表单里粘贴了全新的 API key 点保存,也始终报
failed to read existing <agent> provider credential,没有任何自救路径(UI 上无法清除坏密文,只能手动去safe-storage/目录删.enc文件)。根因链路:
maker:provider:custom:update在写入新 key 前,会先经stageProviderKeys对每个待变更 runtime 做严格快照读取(providerHandlers.ts#L677-L680)。readCustomProviderKeyForMutation,按注释设计「仅 ENOENT 表示没有旧 key,解密失败必须抛错」,目的是防止把暂时不可读的凭证误判为空而永久删除。readCustomProviderHeadersForMutation的请求头路径同理(providerHandlers.ts#L773)。期望行为(建议方向,供维护者裁量):
环境 / Environment
11e02c03上代码路径一致复现步骤 / Steps to reproduce
userData/safe-storage/owner_<id>_provider_key_<provider>_<agent>.enc)。.enc文件内容。failed to read existing claude-code provider credential,保存失败;反复重填 key 均无效,UI 无任何恢复路径。.enc文件移走后再保存 → 立即成功(证明死锁仅由坏密文快照引起)。日志与截图 / Logs & screenshots
(已按模板要求删除凭证与个人数据;
kimi-code为本机自定义供应商 id。)