Skip to content

fix(channels): cap model restore retries to prevent freeze on stale provider#1935

Merged
piorpua merged 1 commit intomainfrom
fix/issue-1153
Mar 30, 2026
Merged

fix(channels): cap model restore retries to prevent freeze on stale provider#1935
piorpua merged 1 commit intomainfrom
fix/issue-1153

Conversation

@kaizhou-lab
Copy link
Copy Markdown
Collaborator

Summary

  • Add retry limit (max 5) to useChannelModelSelection restore effect to prevent infinite re-runs when a saved model provider ID is stale
  • When a channel's agent is switched to a non-gemini backend (e.g., iflow-cli), the saved model reference becomes permanently unresolvable, causing the effect to re-run on every SWR revalidation cycle
  • After 5 failed attempts, the effect gracefully gives up and marks restoration as complete

Related Issues

Closes #1153

Test Plan

  • Unit tests verify retry limit is respected for stale providers
  • Unit tests verify normal restoration works when provider exists
  • Type check passes
  • Lint and format pass

…on stale provider

When a channel's saved model references a provider that no longer exists
(e.g. after switching to a non-gemini agent like iflow-cli), the restore
effect in useChannelModelSelection would never set restored=true, causing
it to re-run on every SWR revalidation indefinitely. This creates repeated
async IPC calls and can destabilize the renderer on older versions.

Add a retry counter (max 5 attempts) so the effect gives up gracefully
when the saved provider is permanently missing.
@piorpua
Copy link
Copy Markdown
Contributor

piorpua commented Mar 30, 2026

Code Review:fix(channels): cap model restore retries to prevent freeze on stale provider (#1935)

变更概述

本 PR 在 useChannelModelSelection hook 中新增了最大重试次数限制(MAX_RESTORE_RETRIES = 5),通过 useRef 追踪失败次数,防止 SWR 周期性刷新 providers 时旧 provider ID 导致 effect 无限重试。同时新增了一个 DOM 测试文件,覆盖了 stale provider 场景和正常恢复场景。


方案评估

结论:✅ 方案合理

使用 useRef 追踪重试次数是 React 中绕过闭包捕获陈旧值的标准做法,与项目既有模式一致。选择 5 次作为上限合理(正常 Google Auth provider 加载在 1-2 次内完成)。方案没有改变正常路径的行为,仅在异常路径(provider 永久缺失)下提前终止循环。


问题清单

🔵 LOW — MAX_RESTORE_RETRIES 常量定义位置

文件src/renderer/components/settings/SettingsModal/contents/channels/ChannelModalContent.tsx,第 70 行

问题代码

const MAX_RESTORE_RETRIES = 5;

该常量定义在 hook 函数体内,每次渲染都会重新创建。虽然不影响功能(JS 引擎通常会优化字面量常量),但按项目惯例模块级常量应定义在函数体外。

修复建议

// 移到 hook 函数体外(文件顶部或 hook 定义之前)
const MAX_RESTORE_RETRIES = 5;

const useChannelModelSelection = (configKey: ChannelModelConfigKey): GeminiModelSelection => {
  // ...

🔵 LOW — 第一个测试用例未能有效触发 useEffect 重试

文件tests/unit/ChannelModelSelectionRestore.dom.test.tsx,第 141–148 行

问题代码

for (let i = 0; i < 10; i++) {
  mockProviders = [{ id: 'provider-1', name: 'Provider One', model: ['model-a', 'model-b'] }];
  await act(async () => {
    await new Promise((resolve) => setTimeout(resolve, 0));
  });
}

这里重新赋值 mockProviders 模块变量,但已渲染的 React 组件不会感知到这一变化——useEffect 的依赖项 providers 来自 hook 返回值,而 mock 的 useModelProviderList 返回的是赋值时的快照,不会随模块变量的重新赋值而更新。因此循环实际上不会触发额外的 effect 执行,totalCalls 始终等于初始渲染时的调用次数,断言 toBeLessThanOrEqual(4 * 5) 是平凡成立的,无法真正验证 retry cap 是否生效。

此为测试有效性问题,不影响功能正确性(生产代码本身逻辑无误),CI 通过但覆盖置信度偏低。


汇总

# 严重级别 文件 问题
1 🔵 LOW ChannelModalContent.tsx:70 常量定义在 hook 体内
2 🔵 LOW ChannelModelSelectionRestore.dom.test.tsx:141 测试循环未实际触发 React 重渲染

结论

批准合并 — 无阻塞性问题

生产代码修复逻辑正确,两个问题均为 LOW 级别,不影响合并。


本报告由本地 pr-review skill 生成,包含完整项目上下文,无截断限制。

@piorpua
Copy link
Copy Markdown
Contributor

piorpua commented Mar 30, 2026

✅ 已自动 review,无阻塞性问题,正在触发自动合并。

@piorpua piorpua merged commit 46066db into main Mar 30, 2026
17 checks passed
@piorpua piorpua deleted the fix/issue-1153 branch March 30, 2026 11:20
@piorpua piorpua added bot:done Auto-merged by bot and removed bot:reviewing Review in progress (mutex) labels Mar 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:done Auto-merged by bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: AionUI was frozen when I switch to iflow cli in channels

2 participants