makecindy / makecindy/cindy

bug: safeStorage 持续不可用时登录态进入无限刷新失败,设备列表长期 401 且无可操作提示

Open
#1,687 4 comments 0 reactions 1 assignee Claimed by @DavidShenXD View on GitHub
bug done
Dominant language
TypeScript
Stars
2.7k
Forks
395
Avg merge
21h 48m
Merged PRs (30d)
776

Description

### 问题描述 / What happened

macOS 上一旦 Electron `safeStorage` 持续不可用,Cindy 仍保持“已登录”界面状态,但 refresh token 无法读取或写入。access token 到期后,远程设备列表持续返回 `401 TOKEN_EXPIRED`,应用每 60 秒重试一次,却不会升级为用户可操作的错误,也不会提示重新授权钥匙串或重新登录。

这会形成长时间“界面看似已登录、实际所有依赖服务端鉴权的请求都失败”的半失效状态。

相关底层问题:#871 记录了 cn/global/dev 共用 `Cindy Safe Storage` 时可能因签名 ACL 不匹配而导致 safeStorage 不可用。本 issue 聚焦的是**无论 safeStorage 因何不可用,上层登录态都缺少持久故障升级和用户提示**,不是重复报告 #871。

### 实际行为

`serverApiClient` 在收到 401 后会调用 `authManager.refresh()`;refresh 因读不到 token 返回失败,于是不重试原请求,直接把 `ServerApiError` 上抛给 `device-link:list-devices`。UI 最终只显示“无法刷新远程设备列表,当前仅显示已加载的内容”。

同一根因还会影响 model access 凭证与 embedding 鉴权,并非仅影响设备列表。

### 根因边界

已证实:safeStorage 不可用 → refresh token 无法读写 → refresh 失败 → access token 到期 → 设备接口持续 401。

尚未完全证实:本机 safeStorage 不可用的底层原因一定为 #871 的 cn/global ACL 冲突;也可能是条目 ACL 损坏或其他钥匙串权限状态。因此不把某一种底层触发条件写成唯一原因。

### 建议的最小修复

- 为连续 `unreadable` 的 refresh 周期计数,并在超过阈值后向 AuthState/renderer 暴露持久凭证库故障。
- 设备列表和其他鉴权请求优先显示该根因,而不是仅显示通用刷新失败。
- 添加测试:`readSafe` 持续返回 null、持久化文件仍存在时,经过 N 个刷新周期后必须产生可操作的降级状态;不能无限 transient,也不能立即清空登录态。
- 添加从 safeStorage 恢复后的重新登录/refresh 验证,防止旧的轮换 refresh token 已失效时继续循环。

### 环境 / Environment

- Cindy 版本或 commit / version or commit: v0.1.28
- 平台与版本 / platform & OS version: macOS(具体系统小版本未记录)
- 安装方式 / install method: packaged app,先运行 v0.1.27,随后通过应用更新并重启至 v0.1.28

### 复现步骤 / Steps to reproduce

1. 在 macOS 上使 Cindy 的 safeStorage 处于不可用状态,例如拒绝钥匙串授权,或使用 #871 所述 ACL 不匹配环境。
2. 启动 Cindy 并完成登录或账号切换。
3. 观察新会话写入:`cindy_auth_session_v1` 因 `safeStorage.isEncryptionAvailable() === false` 无法持久化。
4. 等待当前 access token 到期,或触发运行时刷新。
5. 打开远程设备列表并反复点击“重新读取设备”。

### 期望行为

1. safeStorage 短暂不可用可以重试,但连续 N 次或持续 T 分钟后必须升级为明确状态,例如 `credential_store_unavailable`。
2. Renderer 应显示可操作提示:退出 Cindy、检查“Cindy Safe Storage”访问控制、重启并重新登录。
3. 不应无限保持假登录状态并让所有请求持续 401。
4. 不建议首次失败就直接 `clearAuth`;应避免重新引入瞬时读取失败导致强制登出的历史问题。
5. 日志和诊断应区分:条目不存在、safeStorage 不可用、解密失败、refresh token 被服务端拒绝。

### 日志与截图 / Logs & screenshots

脱敏日志时间线:

- 11:44:首次出现 `safeStorage encryption unavailable (read)`,涉及 `cindy_auth_session_v1` 和 `cindy_auth_refresh_token`。
- 12:55:账号会话已在内存中切换并提交,但 `safeStorage encryption unavailable (write) { key: 'cindy_auth_session_v1' }`,轮换后的 refresh token 未成功落盘。
- 13:50:运行时刷新开始每 60 秒失败,持续出现 `refresh token unreadable but file still present (or encryption unavailable)`。
- 14:23:`GET /api/device-link/devices` 开始返回 `401 TOKEN_EXPIRED`。
- 18:36:safeStorage 读写失败和刷新失败仍在循环,重启、重新登录后也再次发生。

截图显示:UI 只提示“无法刷新远程设备列表,当前仅显示已加载的内容”,右侧仍提供“重新读取设备”,但没有揭示 safeStorage / refresh token 根因,也没有重新授权钥匙串或重新登录入口。

Image

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.