makecindy / makecindy/cindy

[Bug] 启动热更检查被短路后,后台更新可能把 splash 卡在 100%

Open
#3,898 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

从 0.1.72 起,Desktop splash 的启动热更检查被短路。客户端仍会在启动约 10 秒后过后台检查并下载补丁,但 splash 不再根据启动检查结果进入「更新完成,等待自动重启」,默认也不会空闲自动 apply。

实际行为:

- 这是竞态,不是必现卡死。0.1.72 同一构建上,CN 既可以卡住,也可以进入主界面。
- 若 splash 环境检查在约 10 秒后台热更开始前就已经 `passed`,会进入**当前旧版本**主界面;后台仍会把补丁下到 ready,但默认不自动 apply,需要用户点更新横幅才会 `executeRelaunch`。本机一次成功进入是启动后 822ms 就有 `LocalDbGate`,随后 10 秒才后台检查并本地 zip 直接 100%。
- 若 10 秒后台检查开始时 splash 还没 `passed`,进度事件会把状态打成 `updating`,界面停在更新 100%。因为启动检查已被短路成 `hasUpdate: false`,不会进入 `update_done`,也不会自动重启。本机卡住的两次直到退出都没有 `LocalDbGate`,也没有 `executeRelaunch`。
- 这不是 CN 特有逻辑。同机 Global 能升到 0.1.73,是因为它先进入了主界面,再走横幅 `update-relaunch`。两边都是旧包 OTA 到 beta,没有整包下载页。

期望行为:

- 启动阶段要么继续走完整热更闭环:检查 → 下载 → `update_done` → 重启应用。
- 要么明确跳过 splash 热更:后台下载不得把 splash 卡在更新 100%,应放行进入主界面,再由更新横幅 / 用户确认 apply。
- 不能出现「进度已经 100%,但既不进应用也不重启」的死状态。

## 环境 / Environment

- Cindy 版本或 commit / version or commit: 复现于 `0.1.72`(由 `0.1.69` OTA 上来);目标补丁 `0.1.73-beta`。引入提交 [061f5648b](https://github.com/makecindy/cindy/commit/061f5648b6256968a438fd49779a3f9de1123193)(`fix(desktop): skip splash startup update check`),合入提交 [8db3add69](https://github.com/makecindy/cindy/commit/8db3add6918ddd9096336482aa6e781ab43cde4b)(`merge: skip desktop splash startup update check`)。首次进入 `v0.1.72`。当前 `origin/main` 与 `v0.1.73-beta` 仍保留该短路。
- 平台与版本 / platform & OS version: macOS arm64
- 安装方式 / install method: `CindyCN.app` / `CindyGlobal.app` 正式包 + OTA 热更新,无整包下载页

## 复现步骤 / Steps to reproduce

1. 安装已包含启动热更短路的桌面包(`0.1.72` 或之后、尚未修复的构建)。
2. 打开 beta 渠道,使 CDN manifest 上存在更新。
3. 冷启动,并让 splash 的环境检查在 10 秒后台热更开始前尚未 `passed`。
4. 观察 splash 是否进入更新并停在 100%,主界面是否始终不出现。
5. 再冷启动一次:若环境检查很快 `passed`,可进入旧版本主界面,补丁已 ready,但不会自动 apply;需点击更新横幅才会 `executeRelaunch`。

补充:本地已有 ready zip 时,即使断网,后台检查仍可能直接广播 100%。只有本地没有可用补丁且 manifest 失败时,才比较容易放行进应用。不要在 100% 时强杀进程后再立刻启动,可能再次撞上同一状态。

## 日志与截图 / Logs & screenshots

本机 CindyCN 主进程日志(已脱敏)。`0.1.69` 仍会调用启动检查并成功 apply 到 `0.1.72`;进入 `0.1.72` 后启动检查消失。

```text
12:10:40 v=0.1.69 update-check-startup called
12:10:40 Startup: current=0.1.69, latest=0.1.72
12:11:21 auto relaunch conditions met (startup-apply-boundary), applying update v0.1.72
12:11:21 executeRelaunch() called

12:12:04 v=0.1.72 Initialized — first check in 10s
(本段起不再出现 update-check-startup called)
12:12:10 LocalDbGate ready rendererUptimeMs=6570
12:12:19 relaunch requested for update channel change

12:12:21 v=0.1.72 进入主界面
12:12:29 LocalDbGate ready rendererUptimeMs=8012
12:12:31 First background check current=0.1.72, latest=0.1.73, ready=
12:12:48 before-quit(下载 0.1.73 过程中退出)

12:12:56 v=0.1.72 卡住:直到 12:14:00 退出都没有 LocalDbGate
12:13:06 First background check current=0.1.72, latest=0.1.73, ready=
12:13:26 broadcastUpdateProgress p=100
无 executeRelaunch / 无 update-check-startup

12:16:44 v=0.1.72 再次卡住:直到 12:23:15 退出都没有 LocalDbGate
12:16:54 First background check current=0.1.72, latest=0.1.73, ready=
12:16:54 broadcastUpdateProgress p=100(本地 zip 直接完成)
无 executeRelaunch

13:11:26 v=0.1.72 这次进入主界面
13:11:27 LocalDbGate ready rendererUptimeMs=822
13:11:37 First background check,本地 zip 直接 100%
13:12:20 executeRelaunch() called app=/Applications/CindyCN.app zip=cindy-0.1.73-arm64-hotfix.zip
```

对照:Global 升到 `0.1.73` 走的是主界面横幅 `update-relaunch`,不是 splash 启动 apply。`LocalDbGate` 只说明主界面已经挂上,不是开库把 splash 卡死。

## 源码定位

当前 `origin/main` 仍是这个状态。

引入该回归的提交:

- 实际改动:https://github.com/makecindy/cindy/commit/061f5648b6256968a438fd49779a3f9de1123193
`fix(desktop): skip splash startup update check`
- 合入提交:https://github.com/makecindy/cindy/commit/8db3add6918ddd9096336482aa6e781ab43cde4b
`merge: skip desktop splash startup update check`

没有对应 GitHub PR,是 2026-08-31 直合,首次进入 `v0.1.72`。

1. 上述提交把 splash 启动检查改成立即返回:

```ts
// apps/desktop/src/preload/preload.ts:2510-2517
// Splash startup update check is temporarily disabled.
checkAppUpdate: () => Promise.resolve({ hasUpdate: false, action: 'none' }),
```

它不再 `invoke('update-check-startup')`。`0.1.69` 日志里还能看到这条 IPC,`0.1.72` 之后没有。

2. `UpdateService` 仍会在 10 秒后过后台检查。发现更新就 `broadcastUpdateProgress`:

```ts
// apps/desktop/src/main/updateService.ts:155
const FIRST_CHECK_DELAY_MS = 10_000;
// apps/desktop/src/main/updateService.ts:2316-2319
// Initialized — first check in 10s, polling every 30min
```

3. Renderer 仍按「启动热更检查还在」来消费这些进度。`status !== 'passed'` 时把进度写成 `'updating'`;只有启动检查返回 `hasUpdate && action === 'relaunch'` 才会 `update_done`:

```ts
// apps/desktop/src/renderer/contexts/EnvCheckContext.tsx:167-170
if (prev === 'passed') return prev;
// 随后 progress 事件 return 'updating'

// 同文件约 309-312
if (updateResult?.hasUpdate && updateResult?.action === 'relaunch') {
setStatus('update_done');
}
```

4. `EnvCheckGuard` 在非 `passed` 时不挂主界面,所以没有 `LocalDbGate`,也没有更新横幅:

```ts
// apps/desktop/src/renderer/contexts/EnvCheckContext.tsx:371-373
export function EnvCheckGuard({ children }: { children: ReactNode }) {
const { status } = useEnvCheck();
if (status !== 'passed') return null;
```

5. `useSplash.ts` 只在 `splash_update_done` 时调用 `autoRelaunchToUpdate()`。默认 `autoRelaunchOnIdle: false`,卡住时后台也不会空闲自动 apply。

6. 相关但不是同一件事:`5876662ab` 曾让冷启动 apply 不再看空闲开关;PR #1887 想恢复冷启动自动应用已就绪更新,已关闭且未合并。8/31 这次改动是从 splash 侧把启动检查整段关掉,后台下载却还留着。

未完全证实、修复时需要一并核对:部分卡住会话里 `check-environment` 可能也长时间没回到 `passed`。若环境检查已经结束,按当前 stub 理论上会 `setStatus('passed')`。日志能确定的是:这些会话直到退出都没有主界面,同时 splash 被后台热更进度钉在 100%。

## 建议方向

- 不要只短路 preload,却留下 splash 的 `updating` / `update_done` 状态机。
- 若产品仍要 splash 启动热更:恢复 `checkAppUpdate()` → `update-check-startup`,让 ready 补丁回到 `update_done` + 启动 apply。
- 若产品确认 splash 不再做启动热更:后台 `app-update-progress` 在进入主界面前不得把 status 打成 `'updating'`;下载完成应放行主界面,再走更新横幅。
- 回归时至少覆盖:环境检查慢于 10 秒后台热更、本地 zip 已 ready、默认关闭空闲自动重启、CN/Global 两个包装。

Contributor guide

Open the contributing guide

Research direction

Start with apps/desktop/src/preload/preload.ts around checkAppUpdate, then trace the 10-second background check in apps/desktop/src/main/updateService.ts and progress handling in EnvCheckContext.tsx and useSplash.ts. Reproduce with a slow environment check and a ready local zip. Done means the splash cannot remain at 100% without either reaching the main interface or completing the update relaunch flow, including CN and Global packaging.

Written by the indexing model from the issue text.

Assessment

Tech stack
electron, typescript
Domain
desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.