Skip to content

Fix(cli): use npm view for update check instead of update-notifier (#7515) - #7528

Merged
yiliang114 merged 7 commits into
QwenLM:mainfrom
dtometzki:main
Jul 23, 2026
Merged

Fix(cli): use npm view for update check instead of update-notifier (#7515)#7528
yiliang114 merged 7 commits into
QwenLM:mainfrom
dtometzki:main

Conversation

@dtometzki

@dtometzki dtometzki commented Jul 22, 2026

Copy link
Copy Markdown

What this PR does

runGlobalNpm and fetchGlobalNpmUpdateInfo remain and are still
exported for their own use/tests. isGlobalNpmInstallation and
looksLikeNpmPackagePath are removed as dead code in a follow-up
commit, since checkForUpdatesDetailed was their only production
consumer once the branch they served no longer exists.

Why it's needed

Two independent issues currently break the update check:

  1. npm 12 array output (the actual cause of the reported error).
    npm view <pkg> dist-tags.<tag> --json returns a bare string on
    npm 10/11 ("0.20.1"), but wraps it in an array on npm 12+
    (["0.20.1"]). fetchGlobalNpmUpdateInfo's strict string check
    throws on the array shape, producing the reported
    Failed to check for updates: Error: Invalid npm latest version response. This is the root cause confirmed against the reporter's
    npm 12.0.1 environment.

  2. update-notifier's corgi Accept header can 406.
    update-notifier's fetchInfo() sends
    Accept: application/vnd.npm.install-v1+json and
    registry.npmjs.org can answer that with an empty HTTP 406 on
    some paths (see npm/cli#7529).
    With the package-json version currently locked in this repo,
    the request instead sends a multi-value Accept header with
    fallbacks and gets HTTP 200 — so this path is not currently
    failing here, but removing it eliminates the dependency on that
    content-negotiation behavior entirely.

This PR fixes (1) directly (accepting single-element array output)
and addresses (2) as hardening by routing all installs through the
already-tested npm view-based path (fetchGlobalNpmUpdateInfo)
instead of maintaining two fetch mechanisms.

Reviewer Test Plan

How to verify

Reproduce the registry behavior directly (independent of this repo): curl -s -i -H "Accept: application/vnd.npm.install-v1+json" https://registry.npmjs.org/@qwen-code/qwen-code/latest returns HTTP/2 406 with an empty body, while curl -s https://registry.npmjs.org/@qwen-code/qwen-code/latest (no special header) returns a full 200 JSON response. Before this change, a non-global install hitting /update or starting up would log [UPDATE_CHECK] Failed to check for updates: Error: Invalid npm latest version response. After this change, the check instead performs an npm view @qwen-code/qwen-code dist-tags.latest --json call, which is unaffected by the 406 and returns the correct version.

Evidence (Before & After)

N/A — non-UI change, the update check's user-visible output (the "update available" message / /update command output) is unchanged in shape; only the underlying fetch mechanism changed. Verified via automated tests instead (see below):

✓ src/ui/utils/updateCheck.test.ts (41 tests)
✓ src/startup/startup-prefetch.test.ts (31 tests)
✓ src/ui/commands/update-command.test.ts (20 tests)
✓ src/commands/update.test.ts (10 tests)
✓ src/utils/update-relaunch.test.ts (6 tests)
✓ src/utils/handleAutoUpdate.test.ts (28 tests)

Test Files  6 passed (6)
     Tests  136 passed (136)

npx eslint and npx prettier --check on both changed files: clean, no errors.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux

Environment (optional)

npm install --ignore-scripts at repo root, then npx vitest run / npx eslint / npx prettier --check directly in packages/cli. No sandbox/Docker needed — unit tests only, no live network calls in the test suite itself (registry behavior was confirmed separately via curl, outside the test run).

Risk & Scope

  • Main risk or tradeoff: the update check now always shells out to npm view (a child process) instead of making an in-process HTTP request for non-global installs; this is slightly heavier per check but bounded by the existing FETCH_TIMEOUT_MS (5s) and matches what was already done for global installs.
  • Not validated / out of scope: not tested on Windows or macOS locally (Linux only); behavior on registries/mirrors other than registry.npmjs.org (e.g. private mirrors) was not separately re-verified, though fetchGlobalNpmUpdateInfo already has existing test coverage for empty dist-tag responses from mirrors.
  • Breaking changes / migration notes: none for end users. For internal API consumers, checkForUpdatesDetailed() and checkForUpdates() no longer accept a detectGlobalNpm parameter — every current call site in the codebase calls them with zero arguments, so nothing breaks; only test code that explicitly injected a custom detectGlobalNpm needed updating (done as part of this PR).

Linked Issues

Fixes #7515

dtometzki and others added 3 commits July 22, 2026 17:55
…M#7515)

npm 11+ prints `npm view <pkg> dist-tags.<tag> --json` as ["0.20.1"]
instead of "0.20.1", so the strict string check re-broke the update
check with "Invalid npm latest version response". Accept both shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @dtometzki!

Template looks good ✓

Problem: Observed bug with solid evidence. Issue #7515 includes curl reproduction showing registry.npmjs.org returns HTTP 406 for the Accept: application/vnd.npm.install-v1+json header that update-notifier sends. Confirmed independently on the issue thread. This is a real, reproducible breakage affecting every non-global install since v0.20.1.

Direction: Aligned. The update check is core functionality, and the fix reuses the existing fetchGlobalNpmUpdateInfo (npm view) path that already works — no new mechanism introduced. The issue triage already identified this exact fix direction.

Size: Not applicable — changes are in packages/cli/src/ui/utils/, not core paths.

Approach: The scope feels exactly right. Two files, one production + one test, doing the minimum: remove the update-notifier fallback, always use npm view, and handle npm 11+'s array-wrapped output (["0.20.1"]). No unrelated changes, no scope creep. The detectGlobalNpm parameter removal is a natural consequence of eliminating the branch it served.

Moving on to code review. 🔍

中文说明

感谢贡献,@dtometzki

模板完整 ✓

问题: 已观测到的 bug,证据充分。Issue #7515 包含 curl 复现,显示 registry.npmjs.org 对 update-notifier 发送的 Accept: application/vnd.npm.install-v1+json 头返回 HTTP 406。已在 issue 讨论中独立确认。这是自 v0.20.1 起影响所有非全局安装的真实可复现问题。

方向: 对齐。更新检查是核心功能,修复复用了已有的 fetchGlobalNpmUpdateInfonpm view)路径——没有引入新机制。Issue 分诊已确定了完全相同的修复方向。

规模: 不适用——改动在 packages/cli/src/ui/utils/,非核心路径。

方案: 范围恰到好处。两个文件,一个生产代码 + 一个测试,做最小改动:移除 update-notifier 回退,始终使用 npm view,并处理 npm 11+ 的数组格式输出(["0.20.1"])。无无关改动,无范围蔓延。detectGlobalNpm 参数的移除是消除其所服务分支的自然结果。

进入代码审查 🔍

Qwen Code · qwen3.8-max-preview

Reviewed at 9fd141663f9c70f487064633488e444352eba1c5 · re-run with @qwen-code /triage

@dtometzki dtometzki changed the title ix(cli): use npm view for update check instead of update-notifier (#7515) Fix(cli): use npm view for update check instead of update-notifier (#7515) Jul 22, 2026
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal: Given the 406 breakage, I would remove the detectGlobalNpm parameter and updateNotifier import, always route through fetchGlobalNpmUpdateInfo (npm view), and handle npm 11+'s array-wrapped output. The PR does exactly this — no simpler path missed.

Findings: No critical blockers. The change is clean and minimal:

  • The import type { UpdateInfo } from 'update-notifier' is correctly retained — the type is still used by fetchGlobalNpmUpdateInfo's return type and fetchInfoWithTimeout's parameter. Only the runtime import is removed.
  • The npm 11+ array handling (["0.20.1"]"0.20.1") is correct: accepts single-element string arrays, rejects multi-element arrays, non-strings, and arrays of non-strings.
  • The createNotifier closure simplification is straightforward — always wraps fetchGlobalNpm, no branching.
  • checkForUpdates() correctly threads the fetchGlobalNpm parameter through to checkForUpdatesDetailed().
  • All 140 tests pass across 6 related test files (updateCheck, startup-prefetch, update-command, update, update-relaunch, handleAutoUpdate).

One observation (non-blocking): package-json@10.0.1 (bundled with update-notifier@7.3.1) sends a multi-value Accept header (application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*) which includes fallbacks — the registry returns 200 for this. The 406 only triggers with the single-value header. The issue reporter's npm 12 environment may bundle a different package-json version. Regardless, the fix is correct: npm view is more robust and the npm 11+ array format fix is independently necessary (verified below).

Real-Scenario Testing

Root cause confirmation

$ curl -s -o /dev/null -w "HTTP %{http_code}" \
    -H "Accept: application/vnd.npm.install-v1+json" \
    https://registry.npmjs.org/@qwen-code/qwen-code/latest
HTTP 406

$ curl -s -o /dev/null -w "HTTP %{http_code}" \
    https://registry.npmjs.org/@qwen-code/qwen-code/latest
HTTP 200

$ npm view @qwen-code/qwen-code dist-tags.latest --json
"0.20.1"

Before (main branch) — npm 11+ array format breaks

$ npx tsx test-array-format.ts   # simulates npm 11+ output: ["1.1.0"]
Main code FAILS on npm 11+ array format: Invalid npm latest version response

After (this PR) — npm 11+ array format handled

$ npx tsx test-array-format.ts   # same simulated npm 11+ output
PR code handles npm 11+ array format: {"latest":"1.1.0","current":"1.0.0","type":"latest","name":"@qwen-code/qwen-code"}

Update check end-to-end (both versions, npm 10 environment)

$ npx tsx test-update.ts   # main branch
{"status":"up-to-date","currentVersion":"0.20.1"}

$ npx tsx test-update.ts   # PR code
{"status":"up-to-date","currentVersion":"0.20.1"}

Both succeed on npm 10.9.8 because package-json@10.0.1 sends a multi-value Accept header with fallbacks. The 406 failure is specific to environments where the single-value header is sent (the issue reporter's npm 12 setup). The npm 11+ array format fix is independently verified above.

Unit tests

 ✓ src/ui/utils/updateCheck.test.ts (45 tests) 25ms
 ✓ src/startup/startup-prefetch.test.ts (31 tests) 67ms
 ✓ src/utils/update-relaunch.test.ts (6 tests) 108ms
 ✓ src/commands/update.test.ts (10 tests) 3132ms
 ✓ src/ui/commands/update-command.test.ts (20 tests) 28ms
 ✓ src/utils/handleAutoUpdate.test.ts (28 tests) 221ms

 Test Files  6 passed (6)
      Tests  140 passed (140)
中文说明

代码审查

独立方案: 鉴于 406 错误,我会移除 detectGlobalNpm 参数和 updateNotifier 导入,始终通过 fetchGlobalNpmUpdateInfonpm view)路由,并处理 npm 11+ 的数组格式输出。PR 完全这样做了——没有遗漏更简路径。

发现: 无关键阻塞。改动干净且最小:

  • import type { UpdateInfo } from 'update-notifier' 正确保留——类型仍被使用。只移除了运行时导入。
  • npm 11+ 数组处理正确:接受单元素字符串数组,拒绝多元素数组、非字符串等。
  • createNotifier 闭包简化直接——始终包装 fetchGlobalNpm,无分支。
  • 所有 140 个测试通过(6 个相关测试文件)。

一个观察(非阻塞):package-json@10.0.1 发送多值 Accept 头(含回退),registry 返回 200。406 仅在单值头时触发。Issue 报告者的 npm 12 环境可能捆绑了不同版本。无论如何,修复是正确的:npm view 更健壮,npm 11+ 数组格式修复独立必要。

实际场景测试

  • 根因确认:registry 对单值 Accept 头返回 406,无特殊头返回 200
  • 修复前(main 分支):npm 11+ 数组格式 ["1.1.0"] → 失败("Invalid npm latest version response")
  • 修复后(PR 代码):npm 11+ 数组格式 → 成功解析
  • 6 个测试文件 140 个测试全部通过

Qwen Code · qwen3.8-max-preview

Reviewed at 9fd141663f9c70f487064633488e444352eba1c5 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 5/5 — Clean fix for a real bug; minimal, correct, well-tested. Would merge without hesitation.

This is exactly the kind of PR I like to see: the author hit a real bug, filed a clear issue with curl reproduction, then submitted a focused fix that reuses an existing working mechanism instead of adding something new. Two files, one production + one test, no scope creep.

The code is simpler after this change than before — one less branch, one less runtime dependency for version checking. The npm 11+ array format handling is a small but important addition that I verified independently: the main branch throws "Invalid npm latest version response" on ["1.1.0"], the PR code parses it correctly.

The one thing I couldn't reproduce on this machine (npm 10.9.8) is the exact 406 failure with update-notifier, because package-json@10.0.1 sends a multi-value Accept header with fallbacks that the registry accepts. The issue reporter's npm 12 environment behaves differently. But the fix is correct regardless — npm view doesn't depend on content negotiation at all, and the array format fix is independently necessary for npm 11+ users.

140 tests pass across all 6 related test files. No regressions.

中文说明

置信度:5/5 — 干净地修复了真实 bug;最小、正确、充分测试。毫不犹豫地合并。

这正是我喜欢的 PR 类型:作者遇到了真实 bug,提交了带 curl 复现的清晰 issue,然后提交了一个聚焦的修复,复用已有的工作机制而非添加新东西。两个文件,一个生产 + 一个测试,无范围蔓延。

改动后代码比之前更简单——少一个分支,少一个运行时依赖。npm 11+ 数组格式处理是小但重要的补充,我独立验证了:main 分支对 ["1.1.0"] 抛出 "Invalid npm latest version response",PR 代码正确解析。

在此机器(npm 10.9.8)上无法复现 update-notifier 的 406 错误,因为 package-json@10.0.1 发送带回退的多值 Accept 头。但修复无论如何都是正确的——npm view 不依赖内容协商,数组格式修复对 npm 11+ 用户独立必要。

140 个测试全部通过,无回归。

Qwen Code · qwen3.8-max-preview

Reviewed at 9fd141663f9c70f487064633488e444352eba1c5 · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@yiliang114

Copy link
Copy Markdown
Collaborator

Thanks for this — the core approach (routing all installs through npm view instead of update-notifier) is the right call. A few observations from comparing with a local fix I was working on for the same issues:

1. #7520's global-mode rejection is not handled

Issue #7520 has two root causes. The array shape (["0.20.1"] vs "0.20.1") is handled here ✓, but the other half — npm rejecting view --global with {"error":{"summary":"Cannot use view command in global mode."}} — is not. Some npm versions (notably npm 12) reject view when --global is passed, which means fetchGlobalNpmUpdateInfo will throw and the update check fails again for those users.

A targeted fix: catch the rejection (both as a process error and as a JSON error object in stdout) and retry without --global:

let output: string;
try {
  output = await runGlobalNpm([...viewArgs, '--global'], run);
} catch (error) {
  if (!failedWithGlobalModeViewRejection(error)) throw error;
  output = await runGlobalNpm(viewArgs, run);
}
if (output === '') return noNewerVersion;

let parsed: unknown = JSON.parse(output);
if (isGlobalModeViewRejection(parsed)) {
  output = await runGlobalNpm(viewArgs, run);
  if (output === '') return noNewerVersion;
  parsed = JSON.parse(output);
}

This preserves --global as the first attempt (keeping #7224's registry-alignment intent) while gracefully falling back.

2. update-notifier is still in package.json

The runtime import is removed but the dependency remains, so npm install still downloads a package that's never require'd/import'd. The import type { UpdateInfo } could also be replaced with a local interface ({ latest, current, type, name }) to fully decouple. Not blocking, but worth a follow-up cleanup.

3. Dead code: isGlobalNpmInstallation / looksLikeNpmPackagePath

These are still exported and tested but no longer called by checkForUpdatesDetailed (the only production consumer). Could be removed in a follow-up to avoid confusion.

4. Array handling is good

The parsed.length === 1 && typeof parsed[0] === 'string' guard is more defensive than a naive parsed[0] unwrap — nicely catches unexpected multi-element arrays. I'd suggest adding a test case for multi-element array rejection explicitly (the it.each block covers ["1.1.0","1.2.0"] which is good).

Overall this is a solid, minimal fix for #7515. With the global-mode retry added, it would also fully resolve #7520.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed — no blockers. 2 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.

— qwen3.7-max via Qwen Code /review

…n code (QwenLM#7515)

Version checking now goes through npm view for every install type, so:
- replace the update-notifier UpdateInfo type import with a local interface
  and remove update-notifier / @types/update-notifier from dependencies
- remove isGlobalNpmInstallation and looksLikeNpmPackagePath, which no
  longer have any production callers, along with their tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dtometzki

Copy link
Copy Markdown
Author

/review

@dtometzki dtometzki left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested-by Damian Tometzki

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed — no blockers. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.

— qwen3.7-max via Qwen Code /review

@dtometzki

Copy link
Copy Markdown
Author

Hi @wenshao,

ready for approval :-)
Thanks
Damian

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found. LGTM! ✅

— qwen3.7-max via Qwen Code /review

@gwinthis

Copy link
Copy Markdown
Collaborator

Review & Local Verification Report

代码审查

设计评价:依赖精简的正确方向。 本 PR 用 npm view 替代 update-notifier 包进行版本更新检查。

动机: update-notifier 引入了大量传递依赖(@pnpm/npm-confconfigstoreboxenansi-align 等),而实际只需要一个"查询最新版本"的功能。npm view <pkg> version 是零依赖的等价替代。

变更:

  • 移除 update-notifier@types/update-notifier 依赖
  • checkForUpdates() 改为接受可注入的 fetchGlobalNpm 函数(提升可测试性)
  • 测试从 mock update-notifier 改为 mock fetchGlobalNpm
  • package-lock.json 移除约 40 个传递依赖

亮点: 依赖注入使测试更直接,不再需要 vi.hoisted() mock 提升。

结论

LGTM。 减少 bundle 体积,消除不必要的第三方依赖,测试更清晰。

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed — no blockers. Suggestions are inline.

— qwen3.7-max via Qwen Code /review

Comment on lines +22 to +27
export interface UpdateInfo {
latest: string;
current: string;
type: string;
name: string;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The type and name fields on the UpdateInfo interface are never read by any production consumer — they are dead fields inherited from the old update-notifier type shape. — Concrete cost: fetchGlobalNpmUpdateInfo hardcodes type: 'latest' and echoes packageName as name on every return path. All downstream consumers (handleAutoUpdate.ts, update-relaunch.ts, commands/update.ts, ui/commands/update-command.ts) read only .latest. Tests assert the presence of these fields, so future developers must populate and verify values that serve no purpose.

Suggested change
export interface UpdateInfo {
latest: string;
current: string;
type: string;
name: string;
}
export interface UpdateInfo {
latest: string;
current: string;
}

— qwen3.7-max via Qwen Code /review

Comment on lines +265 to +267
const createNotifier = (distTag: 'latest' | 'nightly') => ({
fetchInfo: () => fetchGlobalNpm(name, version, distTag),
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] After removing the update-notifier branch, createNotifier is a factory wrapping a single function call in a throwaway object. The { fetchInfo() } shape was designed to abstract over two implementations; with one remaining, the abstraction adds indirection without value. — Concrete cost: a developer modifying the fetch logic must update three sites (the function, the factory, and the fetchInfoWithTimeout parameter type) instead of one.

Suggested change
const createNotifier = (distTag: 'latest' | 'nightly') => ({
fetchInfo: () => fetchGlobalNpm(name, version, distTag),
});
const fetchGlobalNpmWithArgs = (distTag: 'latest' | 'nightly') =>
fetchGlobalNpm(name, version, distTag);

Then change fetchInfoWithTimeout to accept () => UpdateInfo | Promise<UpdateInfo> directly, and pass () => fetchGlobalNpm(name, version, 'latest') at each call site.

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Maintainer verification — real local build & tests

I built this PR from a clean npm ci at the PR head (4134b7f) and ran the update-check code paths against the live npm registry, plus an A/B of the real functions (BEFORE = origin/main, AFTER = PR head). Summary up front, evidence below.

Verdict: safe to merge, and it does fix #7515 — but the fix that actually resolves the reporter's error is the npm‑12 array handling, not the update-notifier removal. The update-notifier removal is reasonable cleanup (‑936 lines, one code path, drops a heavy transitive tree), but its stated root cause (a 406 that breaks every non-global install) does not reproduce with the package-json version this repo locks. Details + a couple of small description fixes below — none are blockers.

Environment

macOS 24.6 · Node v22.23.1 · local npm 10.9.8 · clean npm ci at PR head · lint/prettier/tsc/vitest run in packages/cli.


1) The reporter's actual bug is reproduced and fixed: npm‑12 array output

The issue quotes the error Invalid npm latest version response. That string is thrown only by the npm view path (fetchGlobalNpmUpdateInfo), i.e. a global install — not by update-notifier. The reporter is on npm 12.0.1, and npm changed npm view <pkg> <field> --json to wrap single-field output in an array:

npm npm view … dist-tags.latest --json
10.9.8 "0.20.1" (bare string)
11.18.0 "0.20.1" (bare string)
12.0.1 (reporter) [ "0.20.1" ] (array)

A/B of the real fetchGlobalNpmUpdateInfo() (injected npm-view stdout, no network):

stdout BEFORE (origin/main) AFTER (PR)
"0.20.1" ✅ OK ✅ OK
["0.20.1"] (npm 12) THROW Invalid npm latest version response OK 0.20.1
["a","b"] (malformed) ❌ THROW ❌ THROW (still rejects — good)
"" (empty/mirror) ✅ no-op ✅ no-op

This is the load-bearing fix (commit 9fd1416) and it exactly reproduces → resolves the reporter's error while keeping malformed-input rejection intact.

root cause

2) The update-notifier "406" root cause does not reproduce here

The 406 is real, but path-specific: the bare corgi Accept header 406s only on the /@qwen-code/qwen-code/latest sub-path. update-notifier@7.3.1 → latest-version@9 → package-json@10.0.1 (the version locked in this repo) requests the full packument registry.npmjs.org/@qwen-code%2Fqwen-code (resolving dist-tags client-side) and sends Accept: …install-v1+json; q=1.0, application/json; q=0.8, */*. I instrumented the actual request — it returns HTTP 200 and fetchInfo resolves. Running the real checkForUpdatesDetailed() against the live registry on the non-global path confirms it:

[BEFORE origin/main]  non-global → update-notifier path → status=up-to-date  (1515ms, no error)
[AFTER  PR#7528]      always npm-view path              → status=up-to-date  (1096ms)

So with current deps, update-notifier returns 200; the removal is hardening/simplification, not a fix for a live 406. (It would protect against a package-json that ever requests /latest, and it removes a large dependency — both fine reasons — but the PR description overstates it as a reproduced failure.)

3) Quality gates — all green at PR head

  • Clean npm ciupdate-notifier genuinely absent from node_modules.
  • vitest run — 7 files / 183 tests passed, including gemini.test.tsx, which still vi.mock('update-notifier', …) for a now-removed dependency — it does not break (the mock is orphaned/never imported). (The 6 files the PR lists = 136; I added gemini.test.tsx = 183 total.)
  • eslint exit 0 · prettier --check clean · tsc --noEmit exit 0.
  • isGlobalNpmInstallation / looksLikeNpmPackagePath removal is safe: on origin/main they are referenced only inside updateCheck.ts + its test — no other production consumer.

A/B + suite


Small description/comment fixes (non-blocking)

  1. Root-cause attribution. The PR body attributes Invalid npm latest version response to update-notifier throwing on a 406. That error string is thrown by the npm view path; the reporter's failure is the npm‑12 array, not update-notifier. Worth reframing the PR so the array fix is the headline and the update-notifier removal is described as cleanup.
  2. PR body says isGlobalNpmInstallation, runGlobalNpm, fetchGlobalNpmUpdateInfo are "unchanged and still exported" — but the head removes isGlobalNpmInstallation (dead-code commit 2e2c1129). Safe, just stale wording.
  3. Code comment in fetchGlobalNpmUpdateInfo says "npm 11+ wraps … in an array" — observed npm 11.18.0 returns a bare string; only npm 12 wraps. Suggest "npm 12+".
  4. Behavioral note (already flagged in the PR): non-global installs now spawn npm view (child process) instead of an in-process HTTPS request. Minor edge case: environments with node present but npm not resolvable would now fail the check where the HTTP path previously worked; bounded by FETCH_TIMEOUT_MS and caught → status: 'error', so acceptable.

Bottom line: ✅ merge-ready — real fix for #7515, tests/lint/types green, dead-code removal is safe. Please tighten the description (points 1–3) so the fix is attributed to the array handling.

中文版本(点击展开)

维护者验证 — 本地真实构建与测试

我在 PR 头 (4134b7f) 做了干净的 npm ci,针对真实 npm registry 运行了更新检查代码路径,并对真实函数做了 A/B(BEFORE = origin/main,AFTER = PR 头)。

结论:可以合并,并且确实修复了 #7515 —— 但真正解决上报者报错的是 npm‑12 数组处理,而不是移除 update-notifier 移除 update-notifier 是合理的清理(‑936 行、单一代码路径、去掉庞大的传递依赖),但其所述根因(一个会让所有非全局安装失败的 406)在本仓库锁定的 package-json 版本下无法复现。以下为证据与几处小的描述修正,均非阻断项。

环境: macOS 24.6 · Node v22.23.1 · 本地 npm 10.9.8 · PR 头干净 npm ci

1)上报者的真实 bug 已复现并修复:npm‑12 数组输出

Issue 中的报错 Invalid npm latest version response 只由 npm view 路径fetchGlobalNpmUpdateInfo,即全局安装)抛出,并非来自 update-notifier。上报者用的是 npm 12.0.1,而 npm 把 npm view <pkg> <field> --json 的单字段输出改成了数组:npm 10/11 返回裸字符串 "0.20.1"npm 12 返回 [ "0.20.1" ]

真实 fetchGlobalNpmUpdateInfo() 的 A/B(注入 npm-view stdout,无网络):["0.20.1"] 时 BEFORE Invalid npm latest version response,AFTER 返回 0.20.1["a","b"] 两者仍正确拒绝;"" 两者都空操作。这是关键修复(提交 9fd1416),精确复现并解决了上报者的报错。

2)update-notifier 的“406”根因在此无法复现

406 确实存在,但与路径相关:裸 corgi Accept 头只在 /@qwen-code/qwen-code/latest 子路径上返回 406。本仓库锁定的 package-json@10.0.1 请求的是完整 packument registry.npmjs.org/@qwen-code%2Fqwen-code(客户端解析 dist-tags),并发送 Accept: …install-v1+json; q=1.0, application/json; q=0.8, */*。我插桩了真实请求 —— 返回 HTTP 200fetchInfo 正常解析。对真实 checkForUpdatesDetailed() 打真实 registry:BEFORE 非全局路径 status=up-to-date(无报错),AFTER 亦 up-to-date。因此在当前依赖下 update-notifier 返回 200,移除属于加固/简化而非修复一个真实的 406。

3)质量门禁 — PR 头全部通过

  • 干净 npm ciupdate-notifier 确实从 node_modules 消失
  • vitest run — 7 文件 / 183 测试通过,含 gemini.test.tsx(其仍对已移除的 update-notifiervi.mock不会报错,因为该 mock 未被引用)。
  • eslint 0 · prettier --check 通过 · tsc --noEmit 0。
  • 删除 isGlobalNpmInstallation/looksLikeNpmPackagePath 安全:在 origin/main 上它们updateCheck.ts 及其测试引用,无其他生产消费者。

小的描述/注释修正(非阻断)

  1. 根因归属:PR 描述把 Invalid npm latest version response 归因于 update-notifier 因 406 抛错;该错误串实为 npm view 路径抛出,真实原因是 npm‑12 数组。建议把数组修复作为主线,update-notifier 移除描述为清理。
  2. PR 描述称 isGlobalNpmInstallation 等“未改动、仍导出”,但头部已删除 isGlobalNpmInstallation(提交 2e2c1129)。安全,仅措辞过时。
  3. 代码注释写 “npm 11+ 会包成数组”,实测 npm 11.18.0 仍是裸字符串,仅 npm 12 才包数组,建议改为 “npm 12+”。
  4. 行为变化(PR 已提及):非全局安装现在会 spawn npm view(子进程)而非进程内 HTTPS 请求。边缘情况:有 nodenpm 不可用的环境会失败;受 FETCH_TIMEOUT_MS 约束并被捕获为 status: 'error',可接受。

结论: ✅ 可合并 —— 真实修复 #7515,测试/lint/类型全绿,死代码删除安全。建议按第 1–3 点收紧描述,使修复归属到数组处理。

Verified locally by the maintainer against the live npm registry; screenshots are Playwright renders of the real command output.

@dtometzki

Copy link
Copy Markdown
Author

Hi @wenshao,

Updated the PR description and code comment per your review — root cause reframed, stale wording fixed, npm 12+ corrected.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found. LGTM! ✅

— qwen3.7-max via Qwen Code /review

@yiliang114

yiliang114 commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Thanks @dtometzki for tracking this down and following through on the review feedback.

The npm 12 singleton-array handling is correct, and removing update-notifier keeps the update-check path simpler. I verified the PR head with a clean install, build, full typecheck, ESLint, and 134 related tests; the required GitHub checks are also green. The remaining no-npm edge case looks like an acceptable low-risk tradeoff. No blockers from my side — LGTM.

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Failed to check for updates (registry error).

5 participants