Skip to content

fix(cli): keep the user's model selection intact on provider template updates - #8873

Closed
ComplexSimply wants to merge 1 commit into
QwenLM:mainfrom
ComplexSimply:fix/provider-update-model-overwrite-8863
Closed

fix(cli): keep the user's model selection intact on provider template updates#8873
ComplexSimply wants to merge 1 commit into
QwenLM:mainfrom
ComplexSimply:fix/provider-update-model-overwrite-8863

Conversation

@ComplexSimply

Copy link
Copy Markdown
Collaborator

What this PR does

Makes the built-in provider template update path leave the user's model selection alone. executeUpdate now drops the install plan's modelSelection unconditionally, so choosing Update all refreshes the provider's model list and version metadata without ever rewriting model.name or clearing model.baseUrl. The completion toast now reports a model switch only when the active model actually changed, instead of predicting one from the plan — which is how the old code could announce Model switched to "<old model>" while writing a different model to disk.

The previous conditional (delete modelSelection only when the plan still offers the current model) was a no-op in every legitimate case and live only in the buggy one: models the provider owns are already carried into the refreshed plan as custom entries — including removed built-ins the user still sits on — so a current model that is absent from the plan is precisely a model that was never this provider's to migrate.

Why it's needed

Fixes #8863 (P1): when the current model.name belongs to another provider (a self-hosted gateway, another vendor on the same protocol), Update all silently rewrote it to the updating provider's first built-in model and cleared model.baseUrl, while the toast claimed the opposite. All 11 built-in providers hit this with different overwrite targets, and with several providers updating in one confirmation each rewrote model.name in turn — the last one in registry order won. This is the unfinished half of #5819 (which caused real monetary damage); the #5835 guard in applyProviderInstallPlan only matches models the plan itself offers, so it never protected models owned elsewhere. The first-install path (/auth, where adopting the provider default is genuine intent) is untouched — this PR changes only the update path in useProviderUpdates.ts.

Reviewer Test Plan

How to verify

  • vitest run src/ui/hooks/useProviderUpdates.test.ts in packages/cli — 20/20. Three new tests cover the single-provider overwrite, the misleading toast, and the multi-provider last-writer-wins case; the pre-existing switches model when previous model is no longer available test pinned the buggy migration and is inverted into does not adopt the provider default when the previous model is gone from the plan (rationale above).
  • Mutation checks: commenting out the delete installPlan.modelSelection fails 4 tests (the 3 new ones + the inverted one); flipping the toast condition (===!==) fails exactly the toast test.
  • Full src/ui/hooks/ slice: 69 files, 1448/1448. tsc --noEmit on packages/cli: clean.
  • Live repro (issue bug(providers): built-in provider update silently overwrites model.name and model.baseUrl when the current model belongs to another provider (#5819 regression) #8863 script, npm run bundle build, isolated $HOME, real TUI dialog via tmux): see Evidence.

Evidence (Before & After)

Before (main @ 4bc75c2) — dialog confirmed with Update all, current model my-own-model owned by no built-in provider:

UI:   ●︎ Token Plan configuration updated successfully. Model switched to "my-own-model".
disk: model.name = "qwen3.7-plus"   (was "my-own-model")
      model.baseUrl = ""            (was "https://my-own-gateway.example.com/v1")

After (this branch) — same script, same dialog, same confirmation:

UI:   ●︎ Token Plan configuration updated successfully.
disk: model.name = "my-own-model"   (unchanged)
      model.baseUrl = "https://my-own-gateway.example.com/v1"   (unchanged)

The update itself still lands in both runs; verified after the fix: providerMetadata.token-plan.version advanced from the stale marker to the current hash, the provider's 15 refreshed built-ins are installed, and both the user's own entry and the provider-prefixed custom (tp-custom) are preserved (17 models total).

Tested on

OS Status
🍏 macOS ⚠️
🪟 Windows ⚠️
🐧 Linux

Environment (optional)

node dist/cli.js from npm run bundle, driven in tmux with an isolated $HOME and the issue's dummy-key settings fixture; no real API traffic.

Risk & Scope

  • Main risk or tradeoff: the update path loses the "adopt the provider default when the current model vanished" migration — deliberately, since the carry of owned models into the plan makes that case unreachable except for models the provider never owned. If a genuine ownership-aware migration is wanted later, it needs the dialog to announce it explicitly (issue bug(providers): built-in provider update silently overwrites model.name and model.baseUrl when the current model belongs to another provider (#5819 regression) #8863's expected-behavior note); that is additive on top of this fix.
  • Not validated / out of scope: the dialog's diff display still computes currentModelAffected/fallbackModel from built-in ids only (display-only, pre-existing); the Model switched to toast branch is kept for the case where refreshAuth genuinely changes the active model.
  • Breaking changes / migration notes: none.

Linked Issues

Fixes #8863. Refs #5819, #5835.

中文说明

本 PR 做了什么

让内置 provider 模板更新路径不再触碰用户的模型选择。executeUpdate 现在无条件丢弃 install plan 的 modelSelection:选择 Update all 只刷新该 provider 的模型列表与版本元数据,任何情况下都不再改写 model.name、不再清空 model.baseUrl。完成提示改为只在活动模型真的变化时才报告切换,而不是按 plan 预测——旧代码正因此一边宣称 Model switched to "<旧模型>",一边把另一个模型写进磁盘。

原来的条件删除(仅当 plan 仍提供当前模型时删 modelSelection)在所有正当情形下都是空操作、只在缺陷情形下生效:该 provider 名下的模型(包括用户仍在使用的已移除内置模型)都会作为自定义条目被携带进刷新后的 plan,因此「当前模型不在 plan 里」恰恰意味着它从来不归本 provider 管、更轮不到本次更新迁移。

为什么需要

修复 #8863(P1):当前 model.name 属于其他 provider(自建网关、同协议下别家厂商)时,Update all 会把它静默改写为该 provider 内置列表第一个模型并清空 model.baseUrl,提示文案却反着说。全部 11 个内置 provider 都有此问题、覆写目标各不相同;多个 provider 在同一次确认里更新时逐个改写 model.name,注册表顺序最后的胜出。这是 #5819(造成过实际资金损失)的未完成修复:#5835applyProviderInstallPlan 加的保护只匹配 plan 自己提供的模型,从未保护过别家模型。首次安装路径(/auth,采纳 provider 默认模型是真实意图)不受影响——本 PR 只改 useProviderUpdates.ts 的更新路径。

验证方式

  • packages/clivitest run src/ui/hooks/useProviderUpdates.test.ts —— 20/20。三个新测试覆盖单 provider 覆写、误导性提示、多 provider 最后写入者胜出;既有的 switches model when previous model is no longer available 测试钉住的是缺陷迁移行为,已反转为 does not adopt the provider default when the previous model is gone from the plan(理由见上)。
  • 变异检查:注释掉 delete installPlan.modelSelection 导致 4 个测试失败(3 个新测试 + 反转测试);翻转提示条件(===!==)恰好导致提示测试失败。
  • src/ui/hooks/ 全量切片:69 文件 1448/1448;packages/clitsc --noEmit 干净。
  • 真实复现(issue bug(providers): built-in provider update silently overwrites model.name and model.baseUrl when the current model belongs to another provider (#5819 regression) #8863 脚本、npm run bundle 构建、隔离 $HOME、tmux 驱动真实 TUI 弹窗):见证据一节。

风险与范围

  • 主要取舍:更新路径不再有「当前模型消失时采纳 provider 默认」的迁移——这是有意的:owned 模型会被携带进 plan,该情形只在模型本就不归该 provider 时可达。若日后需要真正所有权感知的迁移,须让弹窗显式告知(issue 期望行为注记),可在本修复之上增量实现。
  • 未验证/范围外:弹窗 diff 展示仍只按内置 id 计算 currentModelAffected/fallbackModel(纯展示、先前已有);保留 Model switched to 提示分支以覆盖 refreshAuth 真的改变活动模型的情形。
  • 破坏性变更:无。

关联 Issue

Fixes #8863;参考 #5819#5835

… updates

'Update all' in the built-in provider update dialog rewrote model.name
to the provider's first built-in model and cleared model.baseUrl
whenever the current model was not one of that provider's models —
exactly the case where the update has no business touching it (QwenLM#8863,
a QwenLM#5819 regression left open by the QwenLM#5835 guard, which only matches
models the plan itself offers). With several providers updating in one
confirmation, each rewrote model.name in turn and the last one in
registry order won.

A template update is not first-time setup and carries no
model-selection intent: executeUpdate now drops the plan's
modelSelection unconditionally. Models the provider owns are already
carried into the plan (including removed built-ins the user still sits
on), so the previous conditional delete was a no-op in every legitimate
case and live only in the buggy one. The toast now reports a switch
only when the active model actually changed, instead of predicting one
from the plan.
@ComplexSimply

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Aug 10, 2026
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: this is an observed P1 bug, not theory. The linked #8863 carries a full reproduction (isolated $HOME, dummy-key fixture), before/after disk + toast output, a forensic fingerprint (model.baseUrl == "" is only ever written by this path), and a root-cause trace through executeUpdatebuildInstallPlan → the #5835 guard. It is the unfinished half of #5819, which caused real monetary damage. The burden of proof is clearly met.

Direction: aligned. A provider template update is not first-time setup and should carry no model-selection intent, yet the old path silently rewrote model.name and cleared model.baseUrl while the toast claimed the opposite. This is squarely in qwen-code's interest. CHANGELOG already ships this exact direction — core: preserve the selected model when re-applying a provider install plan (#5835) — and this PR completes the case that guard missed. Note: it does touch the model-selection / settings-write surface, so I'm flagging it for maintainer sign-off rather than auto-approving.

Size: 16 production lines (useProviderUpdates.ts, +8/−8) and 177 test lines (+172/−5). Not core-infrastructure paths, far below any size threshold. No Stage 0 concern.

Approach: minimal and focused. It unconditionally drops the plan's modelSelection on the update path and makes the toast report the actual outcome instead of predicting from the plan. The first-install path (/auth) is untouched. I confirmed the legitimate case is preserved: models this provider owns are carried into the refreshed plan as custom entries (customIds), so "current model still offered" behavior is unchanged. No drive-by edits or scope creep.

Risk: no high-revert-risk path matched. The Ubuntu CI unit suite was still running at review time (macOS/Windows/integration are merge-queue-only by design here, so their skipped is expected).

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:这是已观测到的 P1 bug,不是理论问题。关联的 #8863 提供了完整复现(隔离 $HOME、dummy-key fixture)、before/after 磁盘与提示输出、取证指纹(model.baseUrl == "" 只有这条路径会写出),以及贯穿 executeUpdatebuildInstallPlan#5835 保护的根因分析。它是 #5819(曾造成实际资金损失)的未完成修复。举证充分。

方向:对齐。provider 模板更新不是首次安装,本身不应携带选模型的意图,但旧路径会静默改写 model.name、清空 model.baseUrl,提示文案却说反了。这完全符合 qwen-code 的利益。CHANGELOG 已有同方向的先例——core: preserve the selected model when re-applying a provider install plan (#5835)——本 PR 补上了那个保护漏掉的情形。注意:它确实触及模型选择/配置写入这一敏感面,因此转交 maintainer 签核,而非自动批准。

规模:生产代码 16 行(useProviderUpdates.ts,+8/−8),测试 177 行(+172/−5)。非核心基础设施路径,远低于任何规模阈值。无 Stage 0 顾虑。

方案:最小且聚焦。在更新路径上无条件丢弃 plan 的 modelSelection,并让提示按真实结果而非按 plan 预测来报告。首次安装路径(/auth)不受影响。已确认正当情形不受影响:该 provider 名下的模型会作为自定义条目(customIds)被携带进刷新后的 plan,「当前模型仍在列表中」的行为不变。无顺手改动、无范围扩张。

风险:未命中高回滚风险路径。审查时 Ubuntu CI 单元测试仍在运行(此仓库 macOS/Windows/集成测试仅在 merge queue 触发,其 skipped 属预期)。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at 7698cba803eec8bea52f94e4af7b7086f70c4ed8 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Code review

My independent take before reading the diff: a template update carries no model-selection intent, so the update path should strip modelSelection from the plan at the call site, leaving the shared buildInstallPlan/applyProviderInstallPlan (used by first-install /auth and other callers) alone. The PR does exactly this, and I verified the mechanism against the base code rather than taking the description at face value:

  • buildInstallPlan (core provider-config.ts) always sets modelSelection to the list's first model. In applyProviderInstallPlan (core install.ts), the fix(core): preserve the selected model when re-applying a provider install plan #5835 guard only nulls the selection when the plan offers the current model — a model owned by another provider is never offered, so the guard missed precisely the buggy case. Dropping modelSelection before the call closes it.
  • executeUpdate is strictly the update path: findAllPendingUpdates skips any provider without stored providerMetadata.<key>.version, so first-install never reaches this code. The /auth default-adoption behavior is genuinely untouched.
  • The old previousModelStillAvailable conditional was a no-op in every legitimate case: models this provider owns (including removed built-ins the user still sits on) are carried into the refreshed plan via customIds, so an absent current model means it was never this provider's to migrate. The unconditional delete is therefore safe, not a behavior loss.
  • Toast change is correct: the old condition predicted a switch from the plan flag (that's how it announced Model switched to "<old model>" while writing a different model to disk); activeModel === previousModel reports the actual outcome, and the "Model switched" branch survives for the case where refreshAuth genuinely changes the active model.
  • Removing newConfigs/previousModelStillAvailable is clean — no other references — and skipping syncAuthState when there's no selection is right since there's no model change to sync. The added why comment is justified (the non-obvious part is why the unconditional delete is safe).

The tests pin the change the right way: they filter setValue calls for model.name/model.baseUrl and expect none, assert syncAfterAuthRefresh is not called, and still require setValue to fire for the non-model keys (the update itself lands). The inverted pre-existing test plus the three new ones cover single-provider overwrite, the misleading toast, and multi-provider last-writer-wins. No correctness blockers, no convention violations.

Test evidence (this PR's own CI — I do not run PR code)

The Ubuntu unit suite (the only test job that runs on pull_request) was still running at review time; the table below is updated in place by the finalize job once CI settles. The skipped macOS/Windows/integration entries are by design — those jobs are gated to merge_group in ci.yml, so they are not missing coverage on this PR.

Final CI results for 7698cba (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Classify PR ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
route ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

Sandboxed lane for the remaining gap

The unit tests pin the hook logic, but the end-to-end claim — Update all no longer rewrites model.name/model.baseUrl on disk and no longer shows the contradictory toast — currently rests on the author's tmux reproduction on Linux only, which I have not independently re-run (and do not, on the CI path). Sandboxed verification would settle it: @qwen-code /verify for an A/B, mock-free proof against the base build, or @qwen-code /tmux to drive the real dialog and capture the terminal. The author has write access, so either can be triggered directly.

中文说明

代码审查

我在看 diff 之前的独立判断:模板更新不携带选模型的意图,因此应在更新调用点丢弃 plan 的 modelSelection,而不去动共享的 buildInstallPlan/applyProviderInstallPlan(它们还被首次安装 /auth 等路径使用)。PR 正是这么做的,并且我对照基线代码核实了机制,而不是轻信描述:

  • buildInstallPlan(core provider-config.ts)总是把 modelSelection 设为列表第一个模型;applyProviderInstallPlan(core install.ts)里 fix(core): preserve the selected model when re-applying a provider install plan #5835 的保护只在 plan「提供」当前模型时才放弃选择——别家 provider 的模型永远不会被提供,所以保护恰好漏掉了缺陷情形。在调用前丢弃 modelSelection 正好补上。
  • executeUpdate 严格属于更新路径:findAllPendingUpdates 会跳过没有 providerMetadata.<key>.version 的 provider,首次安装到不了这里,/auth 的默认采纳行为确实不受影响。
  • 旧的 previousModelStillAvailable 条件在正当情形下都是空操作:该 provider 名下的模型(包括用户仍在用的已移除内置)会经 customIds 携带进刷新后的 plan,当前模型缺席恰恰说明它从不归本 provider 迁移。无条件 delete 是安全的。
  • 提示修改正确:旧条件按 plan 标志预测切换(这正是它宣称 switched 却写入不同模型的原因);activeModel === previousModel 按真实结果报告,Model switched 分支保留给 refreshAuth 真的改变活动模型的情形。
  • newConfigs/previousModelStillAvailable 移除干净、无其他引用;无选择时跳过 syncAuthState 也正确。新增的 why 注释合理。

测试以正确方式钉住改动:过滤 model.name/model.baseUrlsetValue 调用并断言为空、断言 syncAfterAuthRefresh 未被调用,同时要求其他键的 setValue 仍发生(更新本身落盘)。反转的既有测试加三个新测试覆盖单 provider 覆写、误导性提示、多 provider 最后写入者胜出。无正确性阻塞、无规范违规。

测试证据(来自 PR 自身 CI——不运行 PR 代码)

Ubuntu 单元测试(pull_request 上唯一运行的测试任务)在审查时仍在进行;下表由 finalize 任务在 CI 结束后原地更新。macOS/Windows/集成的 skipped 属预期——这些任务在 ci.yml 中仅在 merge_group 触发,并非本 PR 缺失覆盖。

沙箱验证通道

单元测试钉住了 hook 逻辑,但端到端结论(Update all 不再改写磁盘上的 model.name/model.baseUrl、不再出现矛盾提示)目前依赖作者的 Linux tmux 复现,我未独立复跑(CI 路径也不复跑)。沙箱验证可以补齐:@qwen-code /verify 做对基线构建的 A/B 无 mock 验证,或 @qwen-code /tmux 驱动真实弹窗并抓取终端。作者有写权限,可直接触发。

Qwen Code · qwen3.8-max

Reviewed at 7698cba803eec8bea52f94e4af7b7086f70c4ed8 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — clean review and the fix is right, but this is policy-driven caution rather than doubt: it changes model-selection behavior from a fork, so it needs a maintainer's sign-off.

Stepping back: this is exactly the kind of PR the gate should let through. The problem is real and costly — #8863 reproduces cleanly, and the precedent #5819 burned real money the last time this surface misbehaved. My independent proposal (drop modelSelection at the update call site, leave the shared install-plan machinery alone) is precisely what the PR does, and I couldn't find a simpler path. Sixteen production lines, every one of them necessary, tests that pin the behavior in the right places, and a comment explaining the one non-obvious bit. In six months I'd thank the author, not curse them — the inverted test even documents why the old migration was a bug, not a feature.

Why I'm not approving: the change writes to the model-selection surface (model.name / model.baseUrl semantics on provider updates), and that surface is one where we escalate to a human eye rather than auto-approve — doubly so for a cross-repository PR. Nothing in the review blocks; this is a sign-off request, not a request for changes. The one open item a maintainer may want before merge is the end-to-end confirmation named in my previous comment (@qwen-code /verify or /tmux), since the live TUI/disk reproduction is the author's and the unit suite is mock-based — the unit tests do pin the logic, so this is a belt-and-braces ask, not a gap in the PR itself.

⏸️ Deferring to @yiliang114 (filed #8863 and traced the root cause; core codeowner) — the fix looks correct and complete, but model-selection changes from a fork need a maintainer's call. Needs a human sign-off on this one.

中文说明

置信度:3/5 —— 审查干净、修复正确,这里的保留是政策性的而非存疑:它从 fork 修改了模型选择行为,需要 maintainer 签核。

退一步看:这正是门禁应该放行的 PR。问题真实且代价高——#8863 可完整复现,前车之鉴 #5819 曾因同一敏感面出错烧掉真实资金。我的独立方案(在更新调用点丢弃 modelSelection、不动共享的 install-plan 机制)与 PR 完全一致,且找不到更简路径。生产代码 16 行、每一行都必要,测试在正确的位置钉住行为,注释解释了唯一不显眼之处。六个月后只会感谢作者——反转的测试甚至说明了旧迁移是缺陷而非特性。

不批准的原因:该改动触及模型选择面(provider 更新时 model.name / model.baseUrl 的语义),这一敏感面按政策转交人工审核而非自动批准——跨仓库 PR 更是如此。审查中没有阻塞项;这是请求签核,不是请求修改。maintainer 合并前可能想要的唯一补充是上一条留言中命名的端到端确认(@qwen-code /verify/tmux),因为真实 TUI/磁盘复现来自作者、单元测试基于 mock——单元测试确实钉住了逻辑,所以这是双保险,而非 PR 本身的缺口。

⏸️ 转交 @yiliang114#8863 的报告者与根因分析者、core codeowner)——修复看起来正确且完整,但来自 fork 的模型选择变更需要 maintainer 拍板。

Qwen Code · qwen3.8-max

Reviewed at 7698cba803eec8bea52f94e4af7b7086f70c4ed8 · re-run with @qwen-code /triage

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-dev-bot

qwen-code-dev-bot commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

⚠️ AutoFix round 1 ended without publishing a reportview run.

中文说明

⚠️ AutoFix 第 1 轮结束但未发布报告 —— 查看运行

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Could not produce a passing fix for this feedback (round 1/100). This item now needs a human; the loop stays engaged and still picks up new feedback and base conflicts, but will not retry this item on its own.

What I found before stopping:
Qwen failed during address-review: status 125.

See the Qwen Autofix agent step logs for model/tool output.

Run log: https://github.com/QwenLM/qwen-code/actions/runs/31389561905


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@ComplexSimply

Copy link
Copy Markdown
Collaborator Author

Superseded by #8868, which landed 22 minutes after this PR was opened — closing.

Two independent fixes converged on the same diagnosis within the hour, which is itself nice confirmation of the root cause. The differences in #8868 are improvements over this PR: the migration is retained for the active provider when its model genuinely vanished (keyed on updatesActiveProvider rather than dropped wholesale), and the settings adapter additionally guards security.auth.selectedType against template updates. The toast fix is character-for-character the same.

One case from the issue report that #8868's tests don't pin: the multi-provider batch update, where each executeUpdate in the loop used to rewrite model.name in turn and the last provider in registry order won. The merged gate handles it, but nothing fails if that regresses. I'll follow up with a small test-only PR for that scenario.

For what it's worth, this PR's live before/after TUI run (issue repro script, Update all on a real dialog) confirmed both the corruption on the old build and, re-run against main just now, that #8868 protects the same scenario end-to-end.

中文

#8868 取代(比本 PR 晚 22 分钟提交但先合入)——关闭。

两个独立修复在一小时内收敛到同一诊断,本身就是根因的再确认。#8868 相比本 PR 的差异是改进:迁移在活动 provider 且模型真正消失时被保留(用 updatesActiveProvider 做键,而非整体丢弃),settings adapter 还额外防护了 security.auth.selectedType 不被模板更新改写。toast 修复则逐字符相同。

issue 报告里有一个 #8868 测试未钉住的场景:多 provider 批量更新——循环里每个 executeUpdate 依次改写 model.name,注册表顺序最后者胜出。合入的门控能处理它,但若回归不会有测试失败。我会跟进一个小型 test-only PR 覆盖该场景。

另外:本 PR 的真实 TUI 前后对比(issue 复现脚本 + 真实弹窗 Update all)确认了旧构建上的破坏,也刚在 main 上复跑确认 #8868 端到端保护了同一场景。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+)

Projects

None yet

3 participants