fix(cli): classify nested update-check network errors - #7428
Conversation
Code Review —
|
|
Thanks for the PR! Template looks good ✓ Problem: observed bug with clear evidence. Issue #7423 documents the exact Node 22 error shapes (fetch DNS failure with Direction: aligned — this fixes user-facing error messages for update checks, directly in scope. No CHANGELOG reference needed for a classifier bugfix. Size: not applicable (no core paths touched). Approach: minimal and focused. Two files, one production change (~20 lines) and matching test updates. Every edit maps to a specific misclassification path from the issue. Nothing to cut. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:已观测到的 bug,证据充分。Issue #7423 记录了 Node 22 的真实错误形态(fetch DNS 失败时 方向:对齐——修复更新检查的用户可见错误信息,完全在范围内。 规模:不适用(未触及核心路径)。 方案:最小且聚焦。两个文件,一处生产代码改动(约 20 行)加对应测试更新。每处改动都对应 issue 中的一个具体分类错误路径。无需删减。 进入代码审查 🔍 — Qwen Code · qwen3.7-max Reviewed at |
Code ReviewIndependent proposal: given the two misclassification paths (fetch DNS failure hiding Comparison with the diff: the PR does exactly this. The No correctness issues, no security concerns, no convention violations. The variable shadowing in the Unit tests: 41/41 pass, covering the execFile timeout shape, Real-Scenario TestingBuilt and bundled the CLI, then ran the update command against a non-existent registry host. Before (main build)After (this PR)DNS failure now correctly reports "registry unreachable" instead of the generic "registry error". Matches the PR's stated before/after. 中文说明代码审查独立方案: 针对两条分类错误路径(fetch DNS 失败将 与 diff 对比: PR 完全按此实现。 无正确性问题、无安全隐患、无规范违反。 单元测试:41/41 通过,覆盖 execFile 超时形态、 真实场景测试构建并 bundle CLI 后,对不存在的 registry 主机运行 update 命令。 Before(main 构建)After(本 PR)DNS 失败现在正确报告 "registry unreachable" 而非通用的 "registry error"。与 PR 描述的 before/after 一致。 — Qwen Code · qwen3.7-max Reviewed at |
|
Confidence: 5/5 — clean, minimal fix with a clear reproduction, verified before/after, and tests that match the real error shapes. This is exactly the kind of PR that's easy to review and easy to maintain. The problem is real (linked issue with Node 22 repro), the fix is the minimum needed (unwrap one cause level, detect the killed/SIGTERM shape, reclassify ETIMEDOUT), and the before/after confirms it works end-to-end. In six months this code will read as obvious — "of course we check the cause" — which is the mark of a good bugfix. No reservations. Ships it. ✅ 中文说明置信度:5/5 — 干净、最小化的修复,有明确复现、经验证的 before/after、以及与真实错误形态匹配的测试。 这正是容易审查、容易维护的 PR。问题真实(关联 issue 附 Node 22 复现),修复是最小必要改动(解包一层 cause、检测 killed/SIGTERM 形态、重分类 ETIMEDOUT),before/after 确认端到端有效。六个月后这段代码读起来会理所当然——"当然要检查 cause"——这就是好的 bugfix 的标志。 无保留意见。可以合入。✅ — Qwen Code · qwen3.7-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Corrected tmux E2E Test ReportImportant This replaces the earlier report's evidence boundary. The earlier Before/After used explicit Result
Environment: macOS 15.1.1, Node.js v22.22.0, tmux 3.5a, real interactive Qwen TUI at 200×50. Each run used an isolated empty Scenario 1: real startup background DNS failureThis was a normal interactive startup, not env QWEN_HOME=<isolated-dir> npm_config_registry=http://this-host-does-not-exist-zzz.invalid node dist/cli.js --safe-mode
Before: After: This is the reproduced user-visible improvement: Node 22's nested Scenario 2: real global-npm timeout controlEach revision was installed into an isolated temporary npm global layout and launched through the real production launcher. A local registry accepted the real package GET request and intentionally never responded.
Observed on both revisions: The local server recorded one package request from each run. The requested baseline Verification
Screenshot provenancemacOS screen-capture permission was unavailable to the test process. Each PNG is visibly labeled and was rendered without content changes from its corresponding real |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
— qwen3.7-max via Qwen Code /review
| const matchesCode = (code: string) => | ||
| errors.some( | ||
| (error) => | ||
| (error as NodeJS.ErrnoException).code === code || | ||
| error.message.includes(code), | ||
| ); |
There was a problem hiding this comment.
[Suggestion] Variable shadowing — the .some() callback parameter error shadows the outer function parameter error. Three distinct bindings named error coexist in this function (outer param at line 68, matchesCode callback here, and UpdateCheckTimeoutError .some() callback at line 80).
Concrete cost: a maintainer editing either callback body to reference the original error (e.g. error.name, error.stack, or a log statement) would silently reference the array element instead — producing a logic bug that passes existing tests because the array element is usually the same object.
| const matchesCode = (code: string) => | |
| errors.some( | |
| (error) => | |
| (error as NodeJS.ErrnoException).code === code || | |
| error.message.includes(code), | |
| ); | |
| const matchesCode = (code: string) => | |
| errors.some( | |
| (e) => | |
| (e as NodeJS.ErrnoException).code === code || | |
| e.message.includes(code), | |
| ); |
Apply the same rename on line 80: errors.some((e) => e instanceof UpdateCheckTimeoutError).
— qwen3.7-max via Qwen Code /review
Review —
|
✅ Local build & real-run verification (on Linux)I built this PR from source and verified the fix end-to-end. This notably covers Linux, which the PR marked Environment: Linux 6.12 · Node v22.22.2 · bundled 1 · Unit tests — all green
2 · The mocked fixtures faithfully match REAL Node 22 error shapesI generated genuine errors on this machine (not hand-written mocks) and ran them through the real classifier:
The PR's 3 · Real end-to-end CLI run — unreachable registry (real DNS failure)
Both exit with code
中文说明✅ 本地构建 + 真实运行验证(Linux 平台)我从源码构建了本 PR 并端到端验证了修复。这次特别覆盖了 Linux——PR 描述中该平台标记为 环境: Linux 6.12 · Node v22.22.2 · 从 PR head (截图见上方英文部分。) 1 · 单元测试——全部通过
2 · Mock 的错误形态与 Node 22 真实形态一致我在本机生成了真实的错误对象(不是手写 mock),并喂给真实的分类器:
PR 中的 3 · 真实端到端 CLI 运行——registry 不可达(真实 DNS 失败)
两者退出码均为
🤖 Verified locally with Claude Code (Opus 4.8). This is a post-merge Linux verification record — the PR was already merged. Build/test artifacts are ephemeral. |
|
Released in v0.20.1. |





Note
Post-merge E2E testing corrected part of the original scope. The direct
error.causefix is reproducible through the real startup TUI. The claimed global-npm timeout differential is not: both the #7409 baseline and this PR already renderregistry did not respond within 5sbecause the outer typed timeout surfaces first. #7431 removes the unsupported child-process special case and restores the priorETIMEDOUTclassification.What this PR does
This fixes the reproducible Node 22 fetch error shape behind #7423. Network error codes can live on a direct
error.cause, so the update-check classifier now examines that cause as well as the top-level error.Why it's needed
Node's fetch API reports DNS failures as a top-level
TypeError: fetch failedwhile keepingENOTFOUNDonerror.cause.code. The previous classifier inspected only the top-level code and message, so a real startup background check fell back toregistry errorinstead ofregistry unreachable.The merged revision also included child-process timeout and
ETIMEDOUTclassification changes. Complete global-npm testing after merge did not reproduce the claimed behavior difference, so those two changes are removed by #7431 rather than retained as speculative handling.Reviewer Test Plan
How to verify
cd packages/cli && npx vitest run src/ui/utils/updateCheck.test.ts.npm_config_registry=http://this-host-does-not-exist-zzz.invalid; the background warning should reportregistry unreachableinstead ofregistry error.Evidence (Before & After)
Corrected real-startup tmux evidence and the global-npm timeout control are included in the E2E report comment below.
Tested on
Environment (optional)
macOS 15.1.1, Node.js v22.22.0, tmux 3.5a. Windows and Linux are left to CI.
Risk & Scope
Linked Issues
Fixes #7423
Related to #7409
Follow-up correction: #7431
中文说明
本 PR 可复现的修复是读取 Node 22 fetch 错误的直接
error.cause。真实启动 TUI 中,DNS 失败从 #7409 baseline 的registry error正确变为registry unreachable。合并后完成的 global-npm E2E 纠正了原描述中的 timeout 结论:baseline 与本 PR 都已经显示
registry did not respond within 5s,因为外层类型化超时会先返回。#7431 删除不受完整路径支持的子进程 timeout 特判,并恢复原有的ETIMEDOUT分类。正确的启动 Before/After 图片及 timeout 无差异控制见下方独立测试评论。