* docs(design): /review Aone Code read path (Phase 2) * feat(review): Aone Code read path (Phase 2) Adds an Aone Code provider so /review can review a MaxCompute CR locally. The read path works end to end against a real odps_src CR (verified E2E): fetch-pr fetches `refs/merge-requests/<global-id>/head` and builds the worktree + diff (stats computed locally, Aone advertises none); meta / issue-context / fetch-diff resolve identity, Aone workitem evidence, and the diff via the a1 CLI. The four reader-backed subcommands and fetch-pr select the provider from the clone's remote (or an Aone host), so a GitHub clone is unchanged. Read-only this phase: pr-context / comment-status / presubmit have no Aone backing yet (the run degrades to context-unavailable), and --comment is refused on an Aone target. SKILL.md + code-review.md document the Aone target and the degradations. See docs/design/2026-08-15-review-aone-provider.md. * fix(review): address PR #9226 round-1 Aone review findings Critical: - match-remote: Aone CR URLs use the WEB host (code.alibaba-inc.com) while a clone's remote uses the GIT host (gitlab.alibaba-inc.com) — treat them as one equivalence class (hostsEquivalent) so a codereview URL matches its clone's remote and the worktree flow is reachable; nested-group remotes (group/subgroup/project) now collapse to the last two segments instead of failing to match - registry: an explicit non-Aone host/remote now beats the cwd probe, so an explicitly-GitHub subcommand run from an Aone clone is not hijacked to Aone; hint host is trimmed; the four reader-backed subcommands thread --host into detection (previously dropped); dropped the unwired --platform dead switch - aone parseRemoteUrl: user-less scp remotes (ssh-config/insteadOf), nested groups, a trailing slash after .git, and empty segments now parse; the parse-failure message redacts a user:token@ origin (no credential leak) - aone getCommentBody throws on a missing id (was an indistinguishable empty string); fetchDiff uses gitRaw (512 MiB buffer, no CRLF rewrite, latin1) instead of git() (1 MiB ENOBUFS, CJK byte loss) Suggestions: - aone-client: 120 s timeout, ENOENT branch in the auth check ("install the a1 CLI"), TRANSIENT_RE anchored to HTTP 5xx (bare 502/503/504 misfired on command lines containing those digits), one stderr trace line per retry - fetch-pr: validate pr_number before Number() coercion (1e3 fetched PR 1000), trim --host before detection; submit guard trims --host; comment-body --pr help notes the Aone per-MR requirement - parse-args: nested-group codereview URL grammar; invalid-url warning names both grammars - SKILL.md + code-review.md: Aone paragraph corrected (clone-origin trigger, Agent 0 skipped, test-plan/publish-assets unbacked, pass --host), design doc updated (detection, Agent 0 gating) - tests: aone.test.ts, registry cwd-mock + precedence + parseRemoteUrl cases, remote-match hostsEquivalent + nested collapse, parse-args nested codereview, submit-aone refusal * fix(review): address PR #9226 round-2 Aone review findings Critical: - parse-args: the Aone CR URL grammar is now constrained to Aone hosts (*.alibaba-inc.com) — a /codereview/ URL on any other host hits the fail-closed invalid-url refusal instead of becoming a live PR target (unlike …/pull/<n>, which any GHE host legitimately serves) - aone fetchDiff: merge-bases against a fetched target branch (not a present-but-stale origin/<target>), and the MR-head refspec is now force-fetched (+) so a stale throwaway ref from an interrupted run does not fail the fetch when the head was rewritten (normal AGit-Flow iteration) Suggestions: - fetch-pr: countDiffChangedLines now delegates to the single hunk-state walker in computeDiffStats (the two could not disagree silently); the changedFiles count is pinned on `diff --git` via a binary-file fixture - aone parseRemoteUrl scheme case made explicit + pinned (RFC 3986) - submit.test.ts pins the platform registry to GitHub so the Aone refusal guard neither spawns a real git in the vitest cwd nor couples to the machine's clone origin * fix(review): address PR #9226 round-3 Aone review findings Critical: - registry: hostOfRemoteUrl now makes `user@` optional in the scp branch (user-less scp remotes from ssh-config/insteadOf no longer misroute an Aone clone to GitHub); the token-bearing scp userinfo parses to the host, not an owner - parse-args: the Aone CR-URL host group now requires a REAL subdomain dot boundary (`(?:[A-Za-z0-9-]+\.)+alibaba-inc.com`), so lookalikes (`evilalibaba-inc.com`) hit the fail-closed refusal; a `/pull/<n>` URL on an Aone host is refused too (Aone serves no /pull/ pages) - meta: on a non-GitHub platform an explicit `--repo` without `--host` is refused (no default host off GitHub) instead of emitting the contradictory `platform:aone` + `host:github.com` - aone fetchDiff: spreads PINNED_DIFF_CONFIG/PINNED_DIFF_FLAGS (an un-pinned color.diff=always zeroes computeDiffStats), discloses a failed target-branch fetch via a stderr WARNING, and refuses to diff from a clone of a different repo; scp-form userinfo is redacted in the parse-failure message Suggestions handled: - comment-body: the Aone per-MR `--pr` requirement is enforced before the auth gate (usage errors precede auth) - aone-client: the auth-failure diagnostic surfaces a1's real first stderr line (not the execFileSync preamble) and reports a timeout/kill distinctly - remote-match docstring + registry precedence comment updated to the implemented behavior Deferred to follow-up #9194: the 16 test-gap patterns, headRefOid dead-field removal, MAX_SAFE_INTEGER digit guard, and the refusal-message host branch. * fix(review): address PR #9226 round-4 Aone review findings Critical: - aone fetchDiff + fetch-pr merge-base fetch: the server-controlled target/base branch reached `git fetch` bare — a dash-leading branch name (creatable by full-refname push) parses as an option, so `--upload-pack=<payload>` executed attacker-named code with the reviewer's credentials. Pass `--` to end option parsing and refuse dash-leading values outright on both providers - meta: the no-default-host guard now gates on the FLAG, not the resolved value — a GH_HOST export no longer bypasses it (and an empty-string --host counts as missing); the whole --repo branch's pure resolution moves above the auth gate (usage errors precede auth) - skill: pass --host for EVERY pr-url target including github.com — an omitted hint falls back to the cwd origin probe, which hijacked a github.com review run from an Aone clone (and vice versa); lightweight fetch-diff/pr-context carry the host too - submit: the Aone refusal moves BELOW the authorisation gate and takes the exit-3 + {"posted": false} shape instead of throwing — an unauthorised Aone run now ends as the skill's contract defines, and detection reads the effective host (flag → GH_HOST), so an Aone-pointing GH_HOST export is refused instead of dying opaque inside gh Suggestions handled: - parseRemoteUrl: strip query/fragment (credential channel into repo identity), fix the cleaning order for two-plus trailing slashes after .git, and discard an explicit port instead of folding it into the path - registry: isAoneHost normalizes the trailing-dot FQDN spelling; the cwd probe delegates to lib/git's gitOpt (shared git policy) - aone: the MR-head refspec is stated once (mrHeadRefSpec); resolveRepo quotes git's real error line, not the execFileSync preamble - aone-client: the auth fall-through message is neutral (covers non-auth failures the login hint cannot fix) - fetch-pr: pr_number guard tightened to ^[1-9]\d*$ (no PR zero, no leading zeros, no side effects before the refusal) - the five detection-consuming subcommands' --host describes now state the implemented semantics; SKILL.md/code-review.md read-only phrasing corrected and the false "detection reads the clone's remote, not the URL" claim fixed Tests: dash-leading refusal on both providers, meta guard flip tests (GH_HOST bypass, empty flag, pre-auth), submit exit-3 shape (authorised, unauthorised, padded host, GH_HOST), trailing-dot and /pull/-on-Aone parse refusals, port/query/slash parse cases, fetch-pr zero-number and base-ref refusals. Deferred to follow-up #9194: the single-branch merge-base disclosure (R3-9), cleanup audit skip-in-code (R3-13), URL-form --remote hint (R3-19), publish-assets refusal parity (R3-22), and the data-path deadline translation (R3-25). * chore(review): re-push to re-link PR head after branch recreation * test(review): repin SKILL.md host-rule wording in SKILL.test.ts The round-4 fix rewrote the skill's --host notes (pass --host for every pr-url target, github.com included); three revert-guard tests pinned the old 'add --host <host> for Enterprise' phrasing and reddened the core suite in CI. Repin them at the new wording. * fix(review): address PR #9226 round-5 Critical findings - aone resolveRepo: redactUrl now strips the query/fragment channel too — a ?private_token=… origin carries no @ for the userinfo redaction, so the parse-refusal message echoed the secret the success path strips (test pins the refusal message secret-free) - aone fetchDiff: the merge-base fallback (base = ref~1) DISCLOSES via a stderr WARNING — previously silent, a multi-commit MR got only its last commit served as the complete diff (shallow/single-branch clones hit this; the GitHub path is loud about the same class) - submit: the Aone write-refusal binds the platform in BOTH directions — the authorisation gate now surfaces the recorded target's host, so a recorded Aone host refuses whatever the runtime-effective host resolves to (an ambient GH_HOST export can no longer steer an Aone review into posting at a same-named repo), while a recorded non-Aone pr-url binding is no longer vetoed by the cwd probe from an Aone-origin clone Tests: refusal-message redaction, fallback disclosure (spy calls captured before mockRestore — vitest's restore clears them), bidirectional refusal arms (recorded-Aone + GHE env refuses; recorded-github + Aone cwd posts). * fix(review): address PR #9226 round-6 Critical findings - authorization: the --user-authorized fast path now surfaces the recorded target's host too (best-effort read of the recorded args) — it returned before the args file was read, so recordedHost was always undefined on that path and the 'a recorded Aone host always refuses' invariant leaked: a user-authorised post of a recorded Aone codereview review from a non-Aone cwd with no --host/GH_HOST posted at github.com's same-named repo. Tests pin the fast-path host through the REAL gate and the end-to-end refusal (the witness scenario) - aone: the query/fragment strip now uses [\s\S]* in both redactUrl and parseRemoteUrl — git stores newline-bearing remote URLs, and a plain . stopped at the first \n, letting ?private_token=SECRET\nx smuggle the token past the strip into the parse-refusal message. Tests cover both the parse-success and refusal paths of the smuggle Round-6 is Critical-only per the ~5-round policy (user-confirmed for convergence); the 13 Suggestions are deferred to follow-up #9194. * fix(review): address PR #9226 round-7 Critical findings - fetchDiff's throwaway ref now carries a pid suffix — two concurrent runs for the same MR in one clone shared the name: one session's finally- delete killed the other mid-review (unknown revision), and a pre-existing local branch of the reserved name was force-moved then deleted, reflog and all (race probe: 12/60 failures → 0 with the per-run unique name) - the target/base-ref guards close the refspec channel the dash-only check left open after `--`: a leading `+` parses as a force refspec (fetches the wrong head — stale evidence, no WARNING) and a colon as src:dst (force-moves the throwaway ref or a reviewer-local branch). Both providers now refuse '-', '+', and ':' shapes (probe-confirmed on real fetchDiff incl. the served-wrong-diff and local-branch-overwrite witnesses); tests pin the new channels on both guards - redactUrl and parseRemoteUrl clean userinfo BEFORE the query/fragment strip: a userinfo that itself contains '?' or '#' was truncated mid-credential, leaking the username+secret prefix into the refusal message and making parseable origins unparseable (flip-verified on the witness shapes) Round-7 is Critical-only per the convergence directive; the 8 Suggestions (incl. the 4 bot findings) are deferred to #9194. * fix(review): address PR #9226 round-8 Critical findings - the server-controlled branch-name guards now validate ALLOWLIST-style on both providers (aone.fetchDiff's target, fetch-pr's baseRefName): the denylist admitted HEAD (silent fetch + merge-base through the stale clone-time symref), rev-parse metasyntax (wrong base under a misdescribing warning), ranges, and the empty string (garbled diff-less fallback) — a plain-branch-name shape closes every channel - parseRemoteUrl/redactUrl consume userinfo GREEDILY up to the last @ of the authority — multi-@ and :-/-bearing token userinfo no longer leaks cleartext residue through the refusal messages or folds into the parsed host (take() fails closed on any surviving @); the scp strip admits only a removal that leaves a host: shape behind - fetch-pr's Aone stats backfill moves AFTER the plan/rescue, where diffText is final — the partition-rescue republishing the full range no longer leaves delta-scoped numbers beside a full-range diffPath — and isCollapsedFromUpstream is skipped when the stats are locally derived (one source, not two: the disclosure needs an independent advertised fact, and a delta-scoped round beside the full-range count fired a false collapse) - remote identity is injective again: Aone nested-group targets carry the full group path (parse-args → match-remote --group-path → matchRemotes compares every segment when both sides have three or more), and fetchDiff's origin guard adds the origin's host (Aone family) — a same-named repo in another group or on another platform can no longer pass either gate; SKILL.md passes --group-path for nested targets - meta's discovery branch drops GH_HOST inheritance off GitHub — an ambient GHE export beside an Aone-origin clone no longer vetoes the valid invocation at HOSTNAME_RE; only an explicit --host steers routing Round-8 is Critical-only per the convergence directive; all five findings fixed, no deferrals this round. * fix(review): address PR #9226 round-9 Critical findings - redactUrl is fail-closed BY CONSTRUCTION: split at the last @, redact everything before it — the per-regex redaction kept missing shapes (round-9: URL userinfo with a / in the secret, scp userinfo with a newline, residues with no host: shape all leaked verbatim through the parse-refusal message) - parseRemoteUrl cleans per form and fails CLOSED: URL-form userinfo is bounded to the authority (greedy within it — multi-@ and ?/# inside secrets consumed whole, /-bearing secrets left to fail closed in take), scheme inputs never fall through to the scp grammar (a malformed https://user:pa/ss no longer parses host user); the round-8 scp-strip firing on scheme URLs fabricated coordinates from query-borne and path-borne @ witnesses — all witnesses now parse correctly or refuse - registry hostOfRemoteUrl consumes token-bearing userinfo (':' AND '/' in the secret) on both branches, mirroring aone.parseRemoteUrl — detection no longer parses the credential prefix as the host and misroutes Aone clones to GitHub; detectPlatformKind ranks an explicit --host above the remote-URL hint in BOTH directions (an Aone origin can no longer hijack an explicitly-GitHub invocation into fetching a global MR id from the wrong remote) - nested-group identity is injective in both directions: matchRemotes compares the full group path exactly whenever the target carries one (any length — a 3+-segment target no longer matches a two-segment remote sharing its tail, nor the reverse); Aone CR targets carry the path even at two segments and the canonicalized URL keeps the full path; fetchDiff's origin guard compares the origin's full path against the MR's own detailUrl path (authoritative repo identity, where the seam's ownerRepo is collapsed); the rescue pool keys on the full path and same-id cross-group CR URLs are refused as ambiguous Round-9 is Critical-only per the convergence directive; the 8 Suggestions (R8-6..R8-13) are deferred to follow-up #9194. * fix(review): address PR #9226 round-10 Critical findings - aone.fetchDiff's host arm keys on the CANONICAL Aone-family predicate (new remote-match isAoneHostFamily: port/trailing-dot/case normalized; registry.isAoneHost now delegates to it) — a trailing-dot FQDN clone that detection accepts as Aone can no longer be refused by the diff gate with a misdirecting remedy - the URL cleaning/redaction class is closed structurally, not per shape (sixth consecutive round a new entrance was found): parseRemoteUrl's URL-form userinfo is consumed whole WITHIN the authority (span between // and the first /), and the scp-form userinfo strip + its lookahead are bounded at ?/# — an @ inside a query or fragment value is the credential's own character and can no longer fabricate coordinates from the query tail; redactUrl fails the DISPLAY closed with a constant when the last @ sits after a ?/# marker — the token tail can no longer reach the refusal message (URL/scp/fragment witnesses all pinned) - isPlainBranchName rejects git's pseudo-ref set (FETCH_HEAD/ORIG_HEAD/ MERGE_HEAD/…) on both guards — FETCH_HEAD resolves to the just-fetched PR head (empty diff beside full-range metadata), ORIG_HEAD to an arbitrary ancestor; both shape-legal, both silently wrong - fetch-pr's merge-base probe requires the fetch to have produced the tracking ref — a tag-only baseRefName exits 0 writing only FETCH_HEAD, and the bare-name fallback once merge-based against the reviewer's local tag with baseFetchFailed falsely false; the tag shape now lands in the disclosed state - parse-args: the repo-qualified CR URL outranks a same-number bare spelling as the target in BOTH the rescue pool and positional order — the bare number carries no host, and letting it win flipped detection onto the cwd fallback (a loud refusal at the merge base had degraded to a silent wrong-platform retarget); bare restatements of the URL target are skipped silently, matching the rescue loop's restatement handling Round-10 is Critical-only per the convergence directive (the bot's own ledger is at its round cap); the 6 convergence-posture deferrals named in the review body join follow-up #9194. * fix(review): address PR #9226 round-11 Critical findings - the URL cleaning/redaction surface is closed STRUCTURALLY: one parser, one source of truth — registry.hostOfRemoteUrl now delegates to the canonical aone.parseRemoteUrl (detection and the identity parser can no longer disagree), and the scp branch reads GIT'S OWN grammar (GIT_TRACE-probed: hostinfo ends at the FIRST ':', userinfo carries no ':' or '/') — the last-'@' consumption once parsed a different host than git connects to, letting fetchDiff's same-repo guard pass while git fetched from another server; token-bearing scp shapes now fail closed, and the round-8 detection tests are re-blessed onto shapes git reads that way - the pseudo-ref allowlist is CASE-INSENSITIVE on both twins: on case-insensitive filesystems (macOS/Windows defaults) fetch_head folds onto FETCH_HEAD, resolving the merge-base to the just-fetched MR head (empty diff beside full-range metadata); lowercase spellings refused, pinned - submit.test.ts's file-level setup now saves/clears/restores GH_HOST — the Aone refusal reads the ambient env, and the org's standard intranet export pattern (an Aone-family host) turned 50 of 69 posting tests into refusals - the --user-authorized fast path binds the recorded host to THIS write (same-PR number only — a stale recording of another PR must not supply a host) and scans SIBLING session recordings when the session-scoped args file is absent — the characteristic cross-session publish shape otherwise lost the host and posted a recorded Aone review at github.com's same-named repo (real-gate witness: exit 0, COMMENT filed); tests drive the real gate through a sibling-session fixture - the tracking-ref requirement and both merge-base sites are FULLY QUALIFIED (refs/remotes/…): git resolves unqualified origin/<name> in refs/tags and refs/heads first, so a tag or branch literally named origin/<baseRefName> — a PUSHABLE, server-controlled refname a plain clone auto-carries — shadowed the just-fetched tracking ref and moved the merge base with no disclosure; shadow-tag tests pinned on the resolveMergeBase probe, the fetch-pr seam, and aone.fetchDiff Round-11 is Critical-only per the convergence directive; the bot's own ledger is at its round cap and this round still produced findings — recommend freezing the bot loop and moving to human security review. * fix(review): address PR #9226 round-12 Critical findings - the recorded-args host lookup is HARDENED — the store lives under .qwen/tmp/ beside review worktrees checked out from the PR's own tree, so its content is attacker-influenceable: only s-* session directories are scanned (a malicious PR can no longer plant a root-level args file that binds a host), symlinks are skipped at both the directory and file levels (mirroring writeSkillArgs' O_NOFOLLOW write-side policy), reads are size-bounded, and the host binds only when the recording names the same PR number AND the same repo - the canonical Aone invocation shape (bare global MR id, no URL) can no longer post cross-session without host evidence: a same-number recording with no host binds the recorded --host flag when present (parse-args now records it), and without one the write gate FAILS CLOSED with the exit-3 shape and names the remedy — instead of posting the review at github.com's same-named repo (the probe-verified witness once exited 0 and POSTed) - parse-args: the URL-outranks-bare-number invariant now holds for MIXED shapes — a positional bare number restating the rescue pool's single PR is carved out of hasValidCandidate, so --effort <cr-url> 7 (and both orderings/equals-form) target the CR URL instead of silently retargeting onto the cwd clone's same-number PR; a different number still outranks - aoneReader.resolveRepo refuses an origin outside the Aone host family — an explicit --host can steer detection onto this reader while the cwd clone is a GitHub mirror (the dual-remote migration setup), which once emitted {platform:'aone', host:'github.com'} and queried a1 with the mirror's coordinates; same predicate fetchDiff's origin guard applies - isPlainBranchName (both twins) rejects refs/-prefixed names: legal branch names (check-ref-format --branch) that resolve qualified refs the server controls as fetch/merge-base arguments (refs/remotes/origin/ HEAD is the clone's default-branch symref — wrong base, misdescribing WARNING) - the ref-dwim class is closed at the verified sites: fetch-pr's base probe fetches an EXPLICIT branch refspec (bare names dwim onto same-named tags — exit 0, tracking ref untouched, stale base passing the freshness guard it never refreshed), its fetchedSha/merge-base head reads are refs/heads-qualified (a planted same-name tag can no longer shadow the real head), and aone.fetchDiff's target fetch + merge-base + diff-range reads are qualified the same way Round-12 is Critical-only per the convergence directive (the bot's own ledger is past its round cap). |
||
|---|---|---|
| .github | ||
| .husky | ||
| .qwen | ||
| .vscode | ||
| docs | ||
| docs-site | ||
| eslint-rules | ||
| integration-tests | ||
| integrations/external-context | ||
| packages | ||
| patches | ||
| scripts | ||
| .dockerignore | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| .npmrc | ||
| .nvmrc | ||
| .prettierignore | ||
| .prettierrc.json | ||
| .yamllint.yml | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| Dockerfile | ||
| esbuild.config.js | ||
| eslint.config.js | ||
| eslint.legacy-filenames.mjs | ||
| LICENSE | ||
| Makefile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| SECURITY.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
The open-source AI coding agent that lives in your terminal.
中文 | Deutsch | français | 日本語 | Русский | Português (Brasil) | 한국어
Why Qwen Code?
- Agentic out of the box — Auto-Memory, Auto-Skills, SubAgents, Agent Teams, and MCP. Dynamic workflows, zero setup.
- Open-source, inside and out — The framework and the Qwen models are open-source. They evolve together. No vendor lock-in.
- Multi-protocol — Supports OpenAI, Anthropic, Gemini, and Qwen APIs. Any third-party provider or local model (Ollama / vLLM). Switch at runtime.
- Beyond the terminal — IDE plugins, Desktop app, daemon mode, SDKs, and IM bots (Telegram / DingTalk / WeChat / Feishu).
Tip
Qwen Code is actively iterating on itself — using its own agent and models to file issues, submit PRs, review code, and run tests. Powered by the community, driven by AI.
Installation
Linux / macOS:
curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.sh | bash
Windows:
irm https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.ps1 | iex
Restart your terminal after installation to ensure environment variables take effect.
NPM / Homebrew
NPM (requires Node.js 22+):
npm install -g @qwen-code/qwen-code@latest
Homebrew (macOS / Linux):
brew install qwen-code
Quick Start
qwen # Launch interactive terminal UI
# Inside the session:
/auth # Configure your provider and API key
See the Authentication Guide and Settings Reference for detailed setup.
How to Use Qwen Code
| Mode | Command | Use Case |
|---|---|---|
| Interactive | qwen |
Terminal UI with rich rendering, @file references, slash commands |
| Headless | qwen -p "..." |
Scripts, CI/CD, batch processing — no UI |
| IDE | — | VS Code, Zed, JetBrains |
| Desktop | — | Qwen Code Desktop — GUI for macOS, Windows, Linux |
| Daemon | qwen serve |
Shared agent session over HTTP+SSE (ACP). Multiple clients, one agent. (experimental) Docs |
| SDK | — | TypeScript, Python, Java |
| IM Bot | qwen channel |
Connect to Telegram, DingTalk, WeChat, or Feishu |
SDK example (Python)
import asyncio
from qwen_code_sdk import is_sdk_result_message, query
async def main() -> None:
result = query(
"Summarize the repository layout.",
{
"cwd": "/path/to/project",
"path_to_qwen_executable": "qwen",
},
)
async for message in result:
if is_sdk_result_message(message):
print(message["result"])
asyncio.run(main())
Capabilities
If you know Claude Code, you already know Qwen Code — and then some. We've put significant effort into bringing Qwen Code to feature parity with Claude Code, improving both breadth and reliability across the board.
| Feature | Qwen Code | Claude Code |
|---|---|---|
| SubAgents, Agent Teams, Dynamic Workflows | ✓ | ✓ |
| Auto-Memory, Auto-Skills, Hooks | ✓ | ✓ |
| Built-in Skills (/review, /batch, /loop, /bugfix…) | ✓ | ✓ |
| MCP, Plan Mode, LSP Integration | ✓ | ✓ |
| Auto Mode, Sandbox, Git Worktrees | ✓ | ✓ |
| Computer Use (desktop automation) | ✓ | ✓ |
| IDE Plugins (VS Code / JetBrains / Zed) | ✓ | ✓ |
| SDK | ✓ | ✓ |
| Headless Mode, Session Management | ✓ | ✓ |
| Open-source — model and framework | ✓ | — |
| Multi-protocol (OpenAI / Anthropic / Gemini / Qwen + any provider) | ✓ | — |
| Agent Arena (multi-model head-to-head on same task) | ✓ | — |
Daemon Mode — qwen serve (multi-client shared agent) |
✓ | — |
| IM Channels (Telegram / DingTalk / WeChat / Feishu) | ✓ | — |
Ecosystem
-
Qwen Code Desktop — Official desktop app for macOS, Windows, and Linux
-
AionUi — A modern GUI for command-line AI tools including Qwen Code
-
Gemini CLI Desktop — A cross-platform desktop/web/mobile UI for Qwen Code
-
🦞 Qwen Code Claw — Let other agents (Claude, Codex, etc.) delegate coding tasks to Qwen Code via ACP. Paste this prompt into your agent:
Use Qwen Code as a coding agent. First, fetch and read this skill reference:
https://raw.githubusercontent.com/QwenLM/qwen-code/refs/heads/main/.qwen/skills/qwen-code-claw/SKILL.md
Then follow the instructions inside to install, authenticate, and use Qwen Code via acpx for all coding tasks.
- Aliyun Model Studio CLI — Official CLI for Aliyun's AI platform (
bailian-cli). Extends Qwen Code with image/video generation, knowledge retrieval, app orchestration, and model deployment
Contributing
Contributions are welcome! See CONTRIBUTING.md for guidelines.
Acknowledgments
This project was originally based on Google Gemini CLI v0.8.2. We gratefully acknowledge the Gemini CLI team's excellent work. Starting from Qwen Code v0.1, we stopped syncing with upstream and began independent development as a multi-protocol, multi-platform agent framework with deep integrations for Qwen models and beyond.
