手机端经 device-link 发起的新会话丢失 providerId,凭证降级为 fallback 导致 Opus 必现 api_error
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 395
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
**提交人**: Yifei Zhou (Leo)
**客户端版本**: 0.1.33
---
## 现象
通过手机端(device-link 远程控制)在被控 Mac 上发起新会话时,`startSession` 的 `providerId` 为 `null`,凭证模式降级为 `fallback`,不再使用 Cindy 内已登录的 Anthropic 订阅凭证,而是回落到 Claude Code CLI 自带的登录态。该内置登录态是 Claude **Pro** 计划,Pro 不提供 Opus,于是首轮对话直接 `api_error`,assistant 零输出。
同一台机器、同一个 App 进程(同一 `endpoint`)、同一模型 `claude-opus-5[1m]`,桌面端发起的会话一切正常。间隔 15 秒创建的两个会话,唯一差异就是凭证字段:
| 创建时间 | session | model | providerId | credentialMode | 结果 |
|:--|:--|:--|:--|:--|:--|
| 14:30:38 | `3c4e9096` | claude-opus-5[1m] | **null** | **fallback** | 报错,零回复 |
| 14:30:54 | `d6587434`(桌面端发起) | claude-opus-5[1m] | anthropic | oauth-bearer | 正常 |
| 14:34:12 | `ce37667e` | claude-opus-5[1m] | **null** | **fallback** | 报错,零回复 |
两次失败的时间点都紧跟在一次手机端接入之后(相隔 13~26 秒):
```
14:30:12 [INFO] [device-link-dispatch] control link opened by
14:30:38 → session 3c4e9096 起,providerId=null / fallback
14:33:59 [INFO] [device-link-dispatch] control link opened by
14:34:12 → session ce37667e 起,providerId=null / fallback
```
扫全部本地会话日志,**57 个历史会话中只有这 2 个是 `credentialMode: fallback`,全部为手机端发起,全部失败**;其余桌面端发起的会话 `providerId` 均被正确解析为 `anthropic` / `openai` / `vectorengine`。
## 复现步骤
1. 桌面端 Cindy 登录 Anthropic provider(Cindy 侧订阅凭证),确认桌面端用 `claude-opus-5` 发起会话正常。
2. 前提:机器上 Claude Code CLI 自带的登录账号是 Claude Pro(即不含 Opus 权限的账号)——这一步是暴露该 bug 的关键,若 CLI 内置账号恰好也有 Opus 权限,降级会被静默掩盖。
3. 开启远程控制,手机端接入 device-link。
4. 在手机端**新建**一个会话,模型选 Claude Code + Opus 5,发任意一句话(如 `hi`)。
5. 会话无任何回复。查看 `/logs/sessions//.ndjson`,`startSession` 中 `providerId: null` / `credentialMode: fallback`。
对照:第 4 步改在桌面端新建会话、再用手机接入该会话继续对话,则完全正常(`providerId: anthropic` / `oauth-bearer`)。这也是目前可用的绕过方式。
## 期望行为
手机端发起会话时,`providerId` 与对应的 provider 凭证应与桌面端发起路径保持一致地传递到 host,Opus 正常可用。
## 实际行为
`providerId` 在远程发起链路上丢失为 `null`,host 侧静默降级为 `fallback` 凭证并继续发起请求,最终失败于模型侧。
SDK 返回的错误原文如下,它指向 CLI 登录态而非真正的原因(远程链路未传 provider),因此从这条提示出发排查会被带偏,只有对比 `startSession` 日志字段才能定位:
```
terminalReason : api_error
stopReason : stop_sequence
output : Claude Opus is not available with the Claude Pro plan.
If you have updated your subscription plan recently,
run /logout and /login for the plan to take effect.
```
## 日志位置
- 会话级:`/logs/sessions//.ndjson`(`startSession` 字段对照 + `SDK ◀ turn ended with error`)
- 主日志:`/logs/main-.log`(`device-link-dispatch` 接入时间线)
- 相关:Claude Code CLI 版本 `2.1.219`
日志中的机器名、设备名、owner id、绝对路径已做脱敏替换,需要原始片段可另行提供。
---
**OS**: darwin arm64 (25.5.0)
**界面语言**: zh-CN
Contributor guide
Research direction
Start with the session-level ndjson logs and the main log’s device-link-dispatch timeline, comparing a mobile-created session with a desktop-created one. Trace the device-link startSession path against the desktop path and follow providerId and credentialMode into the host. Done means mobile-created sessions preserve the selected provider and credentials instead of silently falling back.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, authentication
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100