Skip to content

feat: add qwen update and /update commands with auto-update support#5780

Merged
yiliang114 merged 37 commits into
QwenLM:mainfrom
liziwl:main
Jul 9, 2026
Merged

feat: add qwen update and /update commands with auto-update support#5780
yiliang114 merged 37 commits into
QwenLM:mainfrom
liziwl:main

Conversation

@liziwl

@liziwl liziwl commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds qwen update CLI command and /update slash command that check for
new releases and install them automatically (standalone) or guide the user
through manual update steps (npm / yarn / pnpm).

How it works

qwen update
├── Query npm registry for latest version
├── Detect installation type
│ ├── standalone → download archive → SHA256 verify → signature check → smoke test → atomic replace
│ └── npm / yarn / pnpm → show update command (or auto-run npm install)
└── Report result

Install type Behavior
Standalone (install-qwen-standalone.sh / .ps1) Auto-download + verify + smoke test + atomic swap with rollback
npm (writable prefix) Shows npm install -g @qwen-code/[email protected]
npm (requires sudo) Shows sudo npm install -g ...
Git clone Shows git pull
npx / pnpx / bunx Not applicable, shows explanation

Safety

  • SHA256 checksum verification of downloaded archives
  • Ed25519 signature verification of SHA256SUMS (opt-in via QWEN_REQUIRE_SIGNATURE=1)
  • Path-traversal and symlink detection during archive extraction
  • Smoke test: new binary must pass --version before replacing current install
  • Atomic .new.old swap with automatic rollback on failure
  • Windows: deferred swap via helper script (waits for Node process to release file locks)
  • Lockfile prevents concurrent updates

Output examples

$ qwen update
Qwen Code 0.19.1 is up to date!

$ qwen update
Qwen Code update available! 0.19.1 → 0.20.0
Standalone install detected. Attempting to automatically update now...
████████████████████████████████████████ 100%
Update successful! The new version will be used on your next run.

$ qwen update
Qwen Code update available! 0.19.1 → 0.20.0
Installed with npm. Attempting to automatically update now...
Run the following to update:
npm install -g @qwen-code/[email protected]

Files

File Description
packages/cli/src/commands/update.ts New: qwen update CLI command
packages/cli/src/ui/commands/updateCommand.ts New: /update slash command
packages/cli/src/utils/standalone-update.ts New: core update logic (download, verify, extract, atomic swap, rollback)
packages/cli/src/utils/standalone-update-verify.ts New: Ed25519 signature verification
packages/cli/src/utils/standalone-update.test.ts Tests
packages/cli/src/utils/installationInfo.ts New: installation type detection
packages/cli/src/utils/handleAutoUpdate.ts New: background auto-update handler
packages/cli/src/utils/updateEventEmitter.ts New: event bus for update notifications
packages/cli/src/config/config.ts Register qwen update command
packages/cli/src/services/BuiltinCommandLoader.ts Register /update slash command
packages/cli/src/i18n/locales/*.js All 9 locales

Testing

  • All unit tests pass (standalone-update: 20 tests, handleAutoUpdate: 24 tests, updateCheck: 9 tests)
  • qwen --version works
  • qwen update shows version when up to date
  • Shell RC markers compatible with install-qwen-standalone.sh (same begin/end block format)
  • Fish shell path uses set -gx PATH matching install script
  • i18n coverage: 9 languages (en, zh, zh-TW, ja, ru, de, pt, fr, ca)

liziwl and others added 2 commits June 23, 2026 23:28
…ion to update output

## Changes

### Compatibility fixes (standalone-update.ts)
- ensurePathInShellRc: use install script's begin/end block markers (# Qwen Code PATH block begin/end) instead of single-line marker, preventing duplicate PATH entries
- ensurePathInShellRc: fish shell uses set -gx PATH (matching install script)
- ensurePathInShellRc: use single-quoted paths with shell_quote-style escaping
- ensureBinWrapper: use #!/usr/bin/env sh shebang (matching install script)

### Version output
- qwen update: show current version in 'up to date' message (Qwen Code X.Y.Z is up to date!)
- /update slash command: same version display

### Build fix
- esbuild.config.js: add ink/dom and ink/components/CursorContext aliases for ink 7.x compatibility

### i18n
- en.js: add 9 update-related translation keys

Co-Authored-By: Claude <[email protected]>
- zh: Simplified Chinese
- zh-TW: Traditional Chinese
- ja: Japanese
- ru: Russian
- de: German
- pt: Portuguese (Brazil)
- fr: French
- ca: Catalan

Co-Authored-By: Claude <[email protected]>

@wenshao wenshao 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.

[Critical] [test] standalone-update.test.ts:146 asserts expect(content).toContain('#!/bin/sh') but the source at standalone-update.ts:563 was changed to emit #!/usr/bin/env sh. This test will fail on CI. Fix: expect(content).toContain('#!/usr/bin/env sh'). (This line is not in the diff hunk so it's reported here.)

Comment thread packages/cli/src/commands/update.ts Outdated
Comment thread packages/cli/src/ui/commands/update-command.ts Outdated
Comment thread packages/cli/src/utils/standalone-update.ts Outdated
Comment thread packages/cli/src/i18n/locales/en.js
Comment thread packages/cli/src/utils/standalone-update.ts Outdated
Comment thread esbuild.config.js Outdated
Comment thread packages/cli/src/commands/update.ts Outdated
@wenshao

wenshao commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /review

@github-actions

Copy link
Copy Markdown
Contributor
_Qwen Code review request accepted. Review is queued in [workflow run](https://github.com/QwenLM/qwen-code/actions/runs/28066293110)._

Comment thread packages/cli/src/commands/update.ts
Comment thread packages/cli/src/commands/update.ts
Comment thread packages/cli/src/ui/commands/update-command.ts

@wenshao wenshao 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.

⚠️ Downgraded from Approve to Comment: CI failing (Test on windows-latest, macos-latest, ubuntu-latest).

R2 review — 3 additional suggestions beyond the 7 existing comments. Reverse audit converged in round 1 (no new gaps).

Deterministic analysis: tsc: 1 finding (TS2367, already reported R1). eslint: 1 finding (filename-naming on updateCommand.ts — not in eslint.legacy-filenames.mjs; existing camelCase command files are allowlisted but this new file is not).

Needs human review (low confidence):

  • Hardlink entries (entry.type === 'Link') in tar extraction are not validated for path traversal — standalone-update.ts:~258. The post-extraction validateExtractedPaths provides defense-in-depth but realpathSync does not follow hardlinks.
  • ensureBinWrapper TOCTOU between existsSync and writeFileSyncstandalone-update.ts:~559. Low practical risk (user's own bin dir).
  • Bash rc file selection on macOS prefers .bash_profile while the install script prefers .bashrcstandalone-update.ts:~589. Comment claims to match install script but priority is reversed.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/utils/standalone-update.ts
Comment thread packages/cli/src/utils/standalone-update.test.ts
Comment thread packages/cli/src/commands/update.ts
@liziwl

liziwl commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Summary:
- Fixed the update command auto-update-disabled flow and TS2367 narrowing issue.
- Made non-interactive/ACP /update return actionable manual update commands.
- Renamed updateCommand.ts to update-command.ts.
- Fixed standalone update failure exit code handling.
- Added focused tests for CLI update, slash update, tar link validation, wrapper quoting, legacy PATH marker handling, and bash rc selection.
- Hardened tar link target validation for symlinks and hardlinks.
- Made wrapper creation atomic and fixed shell quoting for paths containing single quotes.
- Aligned bash rc selection with the install script and preserved legacy PATH marker compatibility.
- Removed unnecessary ink aliases from esbuild.config.js.
- Restored final newlines in locale files.

Verification:
- npx vitest run packages/cli/src/utils/standalone-update.test.ts packages/cli/src/commands/update.test.ts packages/cli/src/ui/commands/update-command.test.ts --pool=forks --poolOptions.forks.singleFork=true passed: 33 tests.
- npx eslint esbuild.config.js packages/cli/src/commands/update.ts packages/cli/src/commands/update.test.ts packages/cli/src/ui/commands/update-command.ts packages/cli/src/ui/commands/update-command.test.ts packages/cli/src/utils/standalone-update.ts packages/cli/src/utils/standalone-update.test.ts packages/cli/src/services/BuiltinCommandLoader.ts --ext .js,.ts passed.

Please continue reviewing when you have a chance.

Comment thread packages/cli/src/commands/update.test.ts Outdated
Comment thread packages/cli/src/ui/commands/update-command.ts
Comment thread packages/cli/src/commands/update.ts Outdated
Comment thread packages/cli/src/commands/update.test.ts
Comment thread packages/cli/src/i18n/locales/en.js Outdated
Comment thread packages/cli/src/utils/standalone-update.ts
@liziwl

liziwl commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the latest review follow-ups in 10d3f36.

Summary:

  • Fixed the update command test handler args with a typed ArgumentsCamelCase value instead of {}.
  • Added /update fallback guidance when no package-manager update command is available.
  • Extracted shared update-command version replacement into resolveUpdateCommand.
  • Added standalone update success and deferred-path tests.
  • Removed the now-unused Auto-update is disabled... i18n key from all locale files.
  • Limited the Unix post-extraction path validation sweep to symlinks while keeping the full scan for Windows extraction.

Verification:

  • npx vitest run packages/cli/src/commands/update.test.ts packages/cli/src/ui/commands/update-command.test.ts packages/cli/src/utils/standalone-update.test.ts --pool=forks --poolOptions.forks.singleFork=true passed: 37 tests.
  • npx eslint packages/cli/src/commands/update.ts packages/cli/src/commands/update.test.ts packages/cli/src/ui/commands/update-command.ts packages/cli/src/ui/commands/update-command.test.ts packages/cli/src/utils/standalone-update.ts packages/cli/src/utils/installationInfo.ts --ext .ts passed.

Note: npm run typecheck --workspace=packages/cli --if-present still fails locally because packages/core/dist/src/utils/toml-to-markdown-converter*.d.ts has not been built in this checkout, which is the same local build artifact issue seen earlier.

Please continue reviewing when you have a chance.

Comment thread packages/cli/src/utils/standalone-update.ts Outdated
Comment thread packages/cli/src/commands/update.ts Outdated

@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.

The qwen update / /update surface reads cleanly, and the hardening in standalone-update.ts is solid — POSIX single-quote escaping, wx exclusive-create to close the wrapper TOCTOU, hardlink filtering, and the begin/end block-marker idempotency all look right. One blocker and two non-blocking notes.

Blocking — CI is red on all three platforms, but it's a single compile error, not a test failure:

src/commands/update.test.ts(37,7): error TS2322:
Type '{ _: never[]; $0: string; }' is not assignable to type 'ArgumentsCamelCase<Record<string, never>>'.

Record<string, never> gives ArgumentsCamelCase<…> a [key: string]: never index signature that _/$0 can't satisfy, so tsc --build fails and everything downstream goes red. The update handler ignores its args entirely, so the simplest fix is to drop the generic (ArgumentsCamelCase) or cast: { _: [], $0: 'qwen' } as unknown as ArgumentsCamelCase<Record<string, never>>. (The earlier #!/usr/bin/env sh and TS2367 findings look addressed — this is the only remaining build error.)

Non-blocking — explicit update vs. enableAutoUpdate. Both qwen update and /update gate the standalone self-update on enableAutoUpdate !== false. A standalone user who set enableAutoUpdate: false and then explicitly runs qwen update gets "Unable to auto-update this standalone installation. Please reinstall from: …" instead of an update. An explicit command arguably should honor explicit intent regardless of the background-auto-update preference — worth deciding before merge.

Non-blocking — PR description drift. The description lists installationInfo.ts, handleAutoUpdate.ts, updateEventEmitter.ts, and standalone-update-verify.ts as new files, but they aren't in this diff (already present / untouched). The actual change adds commands/update.ts + ui/commands/update-command.ts, registers them, adds resolveUpdateCommand, and hardens standalone-update.ts. Tightening the description would make the history easier to follow.

@yiliang114

Copy link
Copy Markdown
Collaborator

Following up on the enableAutoUpdate note above — I think it's actually the crux of whether these two commands earn their place, so worth spelling out.

Today the startup path already auto-updates everyone who hasn't disabled it: gemini.tsx runs checkForUpdates() → handleAutoUpdate() whenever enableAutoUpdate !== false, and handleAutoUpdate doesn't just notify — for standalone it self-installs (applied next run), and for npm/yarn/pnpm with a writable prefix it actually spawns the install command. So for the default case (auto-update on), qwen update / /update mostly trigger now what the next launch would do anyway — and for standalone it still only applies on restart.

That leaves two cases where an explicit command is genuinely non-redundant:

  1. Users who set enableAutoUpdate: false and want to update on their own schedule.
  2. Headless / scripted invocation (qwen update in CI or cron), which the TUI startup path can't serve.

Both are exactly what the current gating turns off — both commands early-out on enableAutoUpdate !== false, so a standalone user who disabled auto-update gets "Unable to auto-update… please reinstall" instead of an update. As written, the command is closest to a duplicate of the startup path for the people who need it least.

The direction — a first-class update verb — is the right instinct; most standalone CLIs have one. To make it pull its weight, I'd suggest enableAutoUpdate gate only the background/startup behavior, and an explicit qwen update / /update run regardless (the user is asking to update now). Worth settling that contract before merge.

Related, smaller: there are now three paths that check-and-update — startup handleAutoUpdate, /update, and qwen update — and their success messages already differ ("used on your next run" vs "Please restart Qwen Code" vs "applied after you exit this session"). Funnelling all three through one core would stop them drifting.

Not blocking on its own — the build error above is the real merge blocker — but this is the design question I'd want answered.

@liziwl

liziwl commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the latest follow-ups in af5d733.

Summary:

  • Updated the tar filter helper typing to accept the actual tar filter entry shapes (Stats | ReadEntry | { type?: string; linkpath?: unknown }) and safely inspect type/linkpath via property guards.
  • Kept hardlinks rejected outright while preserving symlink target validation.
  • Added a regression test covering fs.statSync(...) entries passed through the tar filter typing path.
  • Raised the SDK daemon browser bundle guard from 125KB to 126KB for the current output.

Verification:

  • npx vitest run packages/cli/src/utils/standalone-update.test.ts packages/cli/src/commands/update.test.ts packages/cli/src/ui/commands/update-command.test.ts --pool=forks --poolOptions.forks.singleFork=true passed: 42 tests.
  • npx eslint packages/cli/src/utils/standalone-update.ts packages/cli/src/utils/standalone-update.test.ts packages/cli/src/commands/update.ts packages/cli/src/commands/update.test.ts packages/cli/src/ui/commands/update-command.ts packages/cli/src/ui/commands/update-command.test.ts packages/cli/src/utils/installationInfo.ts packages/sdk-typescript/scripts/build.js --ext .ts,.js passed.

I also attempted local builds:

  • npm run build --workspace=packages/sdk-typescript still fails locally earlier in SDK TypeScript checks under src/daemon/events.ts / missing @qwen-code/acp-bridge/* declarations, before reaching the bundle-size guard.
  • npm run build --workspace=packages/cli is still killed by SIGKILL in this low-memory environment during tsc --build.

@wenshao wenshao 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.

Additional findings:

handleAutoUpdate.ts:78-82 still contains a character-for-character duplicate of the @latest → version replacement logic that this PR extracted into resolveUpdateCommand in installationInfo.ts. The two copies will silently drift if the nightly-detection heuristic or replacement pattern changes in one place without the other. Consider consolidating by having handleAutoUpdate.ts import and call resolveUpdateCommand.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/commands/update.test.ts Outdated
Comment thread packages/cli/src/commands/update.ts Outdated
Comment thread packages/cli/src/ui/commands/update-command.ts Outdated
Comment thread packages/cli/src/utils/standalone-update.ts
Comment thread packages/cli/src/utils/standalone-update.ts Outdated
@liziwl

liziwl commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the latest unresolved review follow-ups in 25903a1.

Summary:

  • Reused resolveUpdateCommand in handleAutoUpdate so startup auto-update, qwen update, and /update share the same @latest / nightly replacement logic.
  • Added CLI coverage for the updateMessage without updateCommand branch.
  • Added /update coverage for interactive mode with enableAutoUpdate: false, confirming it returns manual update guidance instead of delegating to handleAutoUpdate.
  • Relaxed the Unix shell path guard for single-quoted wrapper/PATH entries while still rejecting NUL/newline characters, with regression coverage for $ paths.
  • Switched the Unix tar extraction filter base to fs.realpathSync(destDir) to match the Windows path and handle symlinked extraction destinations consistently.

Verification:

  • npx vitest run src/commands/update.test.ts src/ui/commands/update-command.test.ts src/utils/standalone-update.test.ts src/utils/handleAutoUpdate.test.ts --pool=forks --poolOptions.forks.singleFork=true passed: 57 tests, 13 skipped.
  • npx eslint packages/cli/src/utils/handleAutoUpdate.ts packages/cli/src/utils/handleAutoUpdate.test.ts packages/cli/src/utils/standalone-update.ts packages/cli/src/utils/standalone-update.test.ts packages/cli/src/commands/update.test.ts packages/cli/src/ui/commands/update-command.test.ts --ext .ts passed.

Note: npm run typecheck --workspace=packages/cli --if-present still fails locally on existing core/CLI API mismatches outside this change (findExistingProviderModels, normalizeClaudeMcpServer, MCP resource helper exports, and RewindRequest.rewindFiles).

Resolved the addressed review threads. Please continue reviewing when you have a chance.

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

1 similar comment
@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@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

@qwen-code /resolve

Merge origin/main into PR QwenLM#5780 branch, resolving conflicts in:
- startInteractiveUI.tsx: kept both update check logic (PR) and
  startPostRenderPrefetches call (main)
- gemini.test.tsx: combined update-related tests (PR) with
  postRenderPrefetches/IDE connection tests (main)
- i18n locale files (9 files): kept both update command translations
  (PR) and history-gap/reload-plugins translations (main)
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code resolved the merge conflicts and pushed the branch update.

Merge Conflict Resolution Summary — PR #5780

Conflicts Found

11 files had merge conflicts between the PR branch and origin/main:

1. packages/cli/src/ui/startInteractiveUI.tsx (2 conflicts)

  • Imports (line 30): HEAD added checkForUpdatesDetailed, handleAutoUpdate, and updateEventEmitter imports. origin/main had no new imports. Resolution: Kept HEAD's imports (they're part of the update feature).
  • Body (line 216): HEAD added an update-check block (if (settings.merged.general?.enableAutoUpdate !== false) { checkForUpdatesDetailed()... }). origin/main added a startPostRenderPrefetches() call. Resolution: Kept both — they're independent features that should coexist. The update check runs first, then startPostRenderPrefetches follows.

2. packages/cli/src/gemini.test.tsx (4 conflicts)

  • Mock setup (line 1910): HEAD added vi.mock() for updateCheck.js and updateEventEmitter.js. origin/main had nothing. Resolution: Kept HEAD's mocks.
  • Import in test (line 1977): HEAD imported checkForUpdatesDetailed in the startup-tasks test. origin/main had nothing. Resolution: Kept HEAD's import.
  • Test body + new tests (line 2008): HEAD added checkForUpdatesDetailed assertion and opened a "should not call checkForUpdates" test. origin/main added mockStartPostRenderPrefetches assertion and opened a "can skip post-render IDE connection" test. Resolution: Combined both assertions in the "should perform all startup tasks" test, then included both complete new tests with their proper setups.
  • Auto-update test + error test (line 2083): HEAD added checkForUpdatesDetailed NOT-called assertion and a "should surface startup update check errors" test. origin/main added mockStartPostRenderPrefetches assertion for the same test. Resolution: Kept both assertions in the "delegates auto-update gating" test, then included HEAD's error-surfacing test.

3. packages/cli/src/i18n/locales/*.js (9 fi

@yiliang114

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@yiliang114
yiliang114 enabled auto-merge July 9, 2026 11:16
yiliang114
yiliang114 previously approved these changes Jul 9, 2026

@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

@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
yiliang114 dismissed stale reviews from qwen-code-ci-bot and themself via c6816ec July 9, 2026 12:10
auto-merge was automatically disabled July 9, 2026 12:10

Head branch was pushed to by a user without write access

@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. Suggestion-level recommendations are in the Suggestion summary comment below.

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Suggestions — commit 30a6b701ccab198bb09092d5319530248c22be58

  • packages/cli/src/utils/standalone-update.ts:497acquireLock fast-path lock release on checkDeferredSwap throw is untested. Add a test: create a .new directory (no pre-existing lock), call acquireLock, expect throw, then assert the lock file was cleaned up (fs.existsSync(lockPath) is false).

— qwen3.7-max via Qwen Code /review

@yiliang114

Copy link
Copy Markdown
Collaborator

@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 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

@yiliang114
yiliang114 enabled auto-merge July 9, 2026 15:11
@yiliang114
yiliang114 added this pull request to the merge queue Jul 9, 2026
Merged via the queue into QwenLM:main with commit e250d6e Jul 9, 2026
48 checks passed
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.

7 participants