makecindy / makecindy/cindy

bug: 移动端新建会话使用默认模型时不携带 providerId,导致 provider_id 落库为 NULL 并回落路由

Open
#2,292 3 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
2.7k
Forks
395
Avg merge
21h 48m
Merged PRs (30d)
776

Description

## 问题描述 / What happened

通过 device-link 从移动端远程控制桌面端时,**新建会话页如果不主动点选模型(直接使用界面上已显示的默认模型)**,创建请求不会携带 `providerId`,导致被控端 `sessions.provider_id` 落库为 `NULL`。该会话随后回落到默认凭证路由,请求走到不支持 Opus 的网关路由,首条消息即失败。

界面上此时显示的是一个具体模型名(例如 `Claude Opus 5`),用户没有任何提示表明"来源尚未选择"。只要在同一个新建页**重新点选一次同一个模型**,`providerId` 就会被携带,`provider_id` 正常落库,会话立即可用。

**期望行为**:新建会话时界面所显示的默认模型,应当与其所属供应商一同生效;不应出现"模型有默认值、来源没有默认值"的不对称,使用户在毫无察觉的情况下创建一个来源为空的会话。

## 对照数据 / Evidence

同一台被控端、同一账号、同一晚连续操作,唯一变量是「是否主动点选模型」。数据取自被控端 `sessions` 表:

| 会话 | 创建时间 | model | provider_id | 操作方式 | 结果 |
|---|---|---|---|---|---|
| d71819ae | 22:58:34 | `claude-opus-5` | **NULL** | **未动默认模型** | **失败** |
| 8159f06b | 22:56:41 | `claude-opus-4-8` | `anthropic` | 主动改选模型 | 成功 |
| f783b9b7 | 23:10:58 | `claude-opus-4-6` | `anthropic` | 主动点选模型 | 成功 |
| b1a538cc | 23:28:34 | `claude-opus-5` | `anthropic` | 主动点选模型 | 成功 |
| d18f3d2c | 23:29:03 | `claude-opus-5` | `anthropic` | 重新点选同一模型 | 成功 |

关键对照:**b1a538cc / d18f3d2c 与 d71819ae 使用的是同一个模型 `claude-opus-5`**,前两者主动点选后 `provider_id=anthropic` 且可用,后者未动默认值则 `provider_id=NULL` 且失败。这排除了「该模型本身不可用」或「账号套餐不支持」的可能,唯一分离变量就是 `provider_id` 是否有值。

补充:该账号为支持 Opus 的套餐,桌面端历史上 `claude-opus-5` + `provider_id=anthropic` 的会话一直正常。

## 根因定位 / Root cause

链路上存在一处不对称:**模型有默认值,来源没有默认值**,且下游每一层都以「未提供」为由跳过。

1. 移动端新建页仅在用户**主动选中某一行**时才把来源写入草稿 —— `apps/mobile/app/sessions/new.tsx:1468-1492` 调用 `resolveRowSelection`,其返回值取自被选中行:

`apps/mobile/src/session/providerModelSections.ts:244-250`
```ts
return {
model: row.model.id,
providerId: row.provider.id,
effort,
fastMode,
};
```

2. 组装创建参数时,`providerId` 是**条件展开** —— 草稿里没有就整个字段不发:

`apps/mobile/src/session/newSession.ts:439-458`
```ts
const providerId = draft.providerId?.trim();
const base = {
agentKind: draft.agentKind,
model: draft.model.trim(),
// ...
...(providerId ? { providerId } : {}),
};
```
注意此处 `model` 无条件携带(有默认值),而 `providerId` 依赖草稿是否被主动赋值。

3. 被控端初次 INSERT 本就不写 `provider_id`(`SessionMeta` 不含该字段),依赖创建后的一次补写:

`packages/maker-core/src/interfaces/session-storage.ts:10-31` — `SessionMeta` 无 `providerId`
`apps/desktop/src/main/maker-host/session-storage.ts:68-94` — INSERT 不含 `provider_id`
`apps/desktop/src/main/maker-ipc/register.ts:5713-5729` — 代码注释已明确记录此结构缺口:
> device-link 远程 create(P2):DesktopSessionStorage.create 从 maker-core SessionMeta 建行,SessionMeta 不含 providerId → 远程新建行 provider_id 恒 NULL。这里在 hydrate 前用 create opts 的 providerId 补写 DB。

4. 而该补写以 `!== undefined` 为前提,字段缺席时整段跳过:

`apps/desktop/src/main/maker-ipc/sessionProviderBootstrap.ts:9-25`
```ts
if (input.providerId !== undefined) {
await input.updateProviderId(input.sessionId, createProviderId);
}
```

5. `provider_id` 为空的运行时后果:

`packages/maker-core/src/agents/credential-mode.ts:20-32`
```ts
if (options.agentKind === 'claude-code' && providerId === 'anthropic') {
return 'oauth-bearer';
}
if (!providerId) {
// 普通 Claude 模型进入既有 fallback
return undefined;
}
```

综上:默认模型未经主动选择 → 草稿无 `providerId` → 创建请求不含该字段 → 补写跳过 → `provider_id` 保持 `NULL` → 回落默认凭证路由 → 请求走到不支持 Opus 的网关并返回 400。

## 可观测性问题 / Observability gap

排查过程中发现两处使该问题难以自助诊断:

1. `create-session invoked` 的日志字段清单不包含 `providerId`(`apps/desktop/src/main/maker-ipc/sessionCreateHandler.ts:100-109`),无法从日志判断创建请求究竟是否携带了来源。
2. 创建后补写失败被视为 non-fatal 且仅记 `log.debug`(`apps/desktop/src/main/maker-ipc/register.ts:5749-5753`),在默认日志级别下完全不可见(实测该机 DEBUG 输出为 0 条)。

建议在 `create-session invoked` 中加入 `providerId`(或至少 `hasProviderId` 布尔),并将补写失败提升到 `warn`。

## 环境 / Environment

- Cindy 版本 / version:v0.1.38
- 被控端 / controlled host:macOS(Darwin 25.5.0),Apple Silicon
- 控制端 / controller:iOS,通过 device-link 远程控制
- 安装方式 / install method:官方 release

## 复现步骤 / Steps to reproduce

1. 移动端通过 device-link 远程控制一台桌面端
2. 在移动端新建会话页,**不点选模型**,直接使用界面上已显示的默认模型(例如 `Claude Opus 5`)
3. 创建会话并发送第一条消息
4. 观察被控端 `sessions` 表中该会话 `provider_id` 为 `NULL`,且消息失败
5. 对照组:重复步骤 1-3,但在新建页**主动点选一次同一个模型**,`provider_id` 落库为具体来源,消息正常

## 相关 / Related

- #1939:本问题失败后,移动端透传的是 Claude Code 的原始错误文案(提示用户执行 `/logout`、`/login`),而非 Cindy 桌面端已有的正确归因。两者是根因与症状的关系:本 issue 解释「为什么请求会走错路由」,#1939 解释「走错之后用户为何无法自行定位」。实际影响叠加后,用户可连续多日无法使用且被引导至错误的恢复操作。

Contributor guide

Open the contributing guide

Research direction

Start at apps/mobile/app/sessions/new.tsx:1468-1492 and apps/mobile/src/session/newSession.ts:439-458, then trace provider handling through apps/desktop/src/main/maker-ipc/sessionProviderBootstrap.ts and register.ts. Verify the default model's provider is preserved when no row is selected, provider_id is populated, and creation logs expose the relevant provider state; use the listed reproduction steps to confirm the first message succeeds.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend, database, mobile, observability
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.