406258d66a fix(core): headless project-hook trust requires the folder-trust feature (#16264)
* fix(core): headless hook trust requires the folder-trust feature

The reporter PoC runs headless (--output-format stream-json with a
one-shot prompt), so the unconditional headless exemption left the
reported execution path open on a fresh default environment. Require
the operator to have enabled security.folderTrust.enabled before the
workspace choice counts as implicit trust; with the feature off,
project hooks are withheld in every mode. Embedders enable the feature
in their managed home settings and keep working via their trust store.

Verified locally against the reporter scenario: default fresh home ->
marker absent; feature on + trust-store grant -> marker written.

Refs #14746

Co-authored-by: Qwen-Coder <[email protected]>

* refactor(core): unify the project-hook gate on explicit trust

The headless exemption branch never changed an observable outcome: the
CLI settings layer already drops workspace settings for an undecided
folder once the feature is enabled, and with the feature off the
explicit predicate is false anyway. Replace both branches with the
single explicit-trust rule so interactive and non-interactive callers
share one contract. Behavior is unchanged; verified against the
reporter PoC (default fresh home withheld, trusted path executes).

Co-authored-by: Qwen-Coder <[email protected]>

* fix(cli): address review findings on the unified hook trust gate

R1-1: the headless feature-off test wrote folderTrust into workspace
settings, where WORKSPACE_RESTRICTED_SETTINGS strips it — the rig user
scope stayed enabled and the test never reached the named gate. Write
the feature-off decision into the user-scope home file instead.

R1-2: the docs sentence overclaimed; IDE-granted trust loads hooks with
the feature off. State the predicate as shipped.

R1-3: headless withholding was silent for the newly gated population.
Emit one stderr line from the non-interactive entry and a debug-log
warn from the registry when the trust gate is why the scope is absent
(getTrustWithheldProjectHooks, diagnostics only).

Verified: reporter PoC withheld with the stderr line present; core
1052 passed; tool-hook integration 14 passed.

Co-authored-by: Qwen-Coder <[email protected]>

* feat(web-shell): surface trust-withheld hooks in the workspace overview

The hooks listing already marks why a project hook is disabled, but the
ACP status serializer dropped the reason, so the web-shell overview only
showed a count while trust-withheld hooks silently did not run. Carry
disabledReason through ServeHookEntry/DaemonHookEntry (optional, older
daemons unaffected), count untrusted hooks in the summary, flag the
facet, and name the remedy in the tooltip (en + zh).

Co-authored-by: Qwen-Coder <[email protected]>

* test(web-shell): pin the trust-withheld hooks tooltip wording

Component-level witness for the untrusted-hooks facet: the row shows
the withheld count and the title names the remedy. The sidebar hooks
facet is opt-in for embedding hosts, so the jsdom suite is the
reachability-independent pin.

Co-authored-by: Qwen-Coder <[email protected]>

* test(cli): align fixtures with the surfaced hook state

The workspace-hooks status test now expects the disabledReason the
serializer carries, and the headless-warnings config stub provides the
new getTrustWithheldProjectHooks accessor.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(cli): tolerate partial config stubs in the headless hook notice

The resumed-transcript and stream-json housekeeping tests use minimal
config stubs; optional-chain the two accessors the notice reads, and
provide the accessor on the remaining isInteractive stubs. All 124
llm.test.tsx cases pass.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(hooks): make the withheld-hooks surfaces reachable and equivalent

Review findings on the status surfaces: the listing never carried
disabledReason untrusted for settings project hooks (the trust gate
keeps them out of the registry, and only agent/session rows were
trustGated), so the web-shell hint was unreachable; buildHooksListing
now appends the diagnostics block as disabled (untrusted) rows, with a
real-listing test. The headless stderr notice now mirrors the TUI
predicate (raw workspace hooks non-empty, exclude disableAllHooks and
safe/bare), covering the feature-on undecided case the Config accessor
cannot see. The facet value stays language-neutral; the localized
withheld count and remedy live in the tooltip. Core 1035, llm 124,
web-shell sidebar 51 tests pass.

Co-authored-by: Qwen-Coder <[email protected]>

* test(web-shell): browser witness for the withheld-hooks status surface

Real Chromium e2e: open Daemon Status via the rail More popover, switch
to Diagnostics, and assert the workspace.hooks section renders with the
withheld count from a report shaped like the real daemon output.
Screenshot retained as test output.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(hooks): fold the deferred approval follow-ups into this PR

Per review direction, land the #16295 items here instead of a follow-up:

- Explicit projectHooksApproved state: a stored approval keeps execution
  authority inside a command-line --yolo session (previously the trust
  gate withheld it because the pending flag is undefined while exempt).
  The flag never doubles as nothing-to-approve; mid-session decisions
  update it. Tests pin yolo+approved executes, yolo+unapproved withholds.
- Display-only raw workspace block: with the feature on and the folder
  untrusted the settings layer drops the workspace scope, so the listing
  could not show the withheld hooks. projectHooksDisplayOnly feeds only
  getTrustWithheldProjectHooks, never the registry; test pins both halves.
- Variable-source wording: the dialog hint now says environment (project
  .env/settings.env or shell), not only the approved project env.

Per-hook approval granularity stays out: it is a feature iteration, not
a correctness fix. Core 1017+1053, cli config 565, approvals 50, llm 127
pass.

Co-authored-by: Qwen-Coder <[email protected]>

* style: prettier the reworded dialog hint

Co-authored-by: Qwen-Coder <[email protected]>

* fix(cli): /hooks list shows withheld project hooks in ACP/web-shell

The non-interactive /hooks list read only the registry, so an ACP
session (web-shell) with unapproved project hooks answered \"No hooks
configured\" — the user could not see what was withheld or why. The
list now includes the approval/trust diagnostics blocks, marked
disabled (not approved / rejected / folder not trusted). Verified
against a real ACP session: the withheld SessionStart hook is listed
with its reason. Tests pin all three reasons.

Co-authored-by: Qwen-Coder <[email protected]>

* feat(web-shell): explicit project-hooks approval prompt for withheld hooks

Web Shell previously had no interactive surface for the project-hooks
approval gate: users had to run /hooks manually. The daemon now reports
the pending decision (with sanitized hook lines) on /workspace/hooks,
including on a cold daemon and when folder trust withholds the workspace
scope, and a dedicated POST /workspace/hooks/approve route (plus the
workspace-qualified twin) records the same hash-bound decision the TUI
dialog writes. The Web Shell renders an Approve / Keep-disabled banner
from that state. This is deliberately not session/request_permission:
automation clients auto-answer permission prompts, a trust decision must
be an explicit user act, and approving never grants folder trust.

Co-authored-by: Qwen-Coder <[email protected]>

* refactor(web-shell): render hooks approval with the shared ToolApproval card

Replace the custom full-width alert banner with the existing ToolApproval
card in the standard approval slot above the composer, so the hooks trust
decision looks and behaves like every other approval: same panel, option
list, keyboard handling, and safe-default focus. The card supplies a
project_hooks request with decision-specific option labels; ToolApproval
localizes its heading and question for that tool name.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(web-shell): hooks approval probe reads once and polls only while pending

The approval card polled /workspace/hooks unconditionally, adding
background metadata reads the workspace-overview quiet-period contract
forbids. Read once per mount/workspace/settings change, poll only while a
decision is pending, and let the E2E quiet-period test settle the initial
probe before counting sidebar traffic.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(hooks): apply the persisted decision once and to every live session

Local review + ponytail over the PR diff:

- The ACP child no longer re-derives and re-writes the approval hash on
  the approve control. The daemon persists the decision for the exact
  block the user reviewed; a second write could bind it to a block that
  changed since the review. The child only applies the decision, to
  every live session config, not just the bootstrap one.
- The raw-workspace review subject hashes the project env files
  regardless of current folder trust, so a decision recorded while
  untrusted still matches after the folder is trusted.
- The headless trust warning no longer doubles with the approval-gate
  warning for a pending block; the registry debug-log duplicate and its
  interface member are removed (the stderr line and listings already
  report the withheld state).
- Drop dead test stubs for an accessor llm.tsx never reads, a redundant
  type member, and a stray blank-line deletion.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(hooks): scope the approve decision to the admission-bound workspace

The per-session fan-out matched every live config in the child. Key it
on the admission-bound workspace root (the same root the settings
handlers use) against the request's cwd instead: session/cd moves the
live target dir, and a decision recorded for one workspace must not
clear or set the gate on a session admitted under another. Regression
test covers same-workspace, cd-moved, and foreign-workspace configs.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(cli): bind the project-hooks approval to the reviewed block

The daemon's approve route re-read .qwen/settings.json at click time and
persisted the hash of that fresh read, while the ACP child applied the
decision to the block it rendered at startup. A file change inside that
window (git pull, another session, the workspace's own agent) recorded an
approval for hooks the user never saw, and left the card pending because
the stored hash matched neither side.

The child now persists against the same read that produced the status cell
it rendered, so the reviewed block and the recorded block are the same
object. The daemon only falls back to its own read when no child answered,
which is the cold-daemon case where that read is what the client saw.

Also unbreaks the web-shell lane: App.test.tsx's daemon-react-sdk mock had
no useOptionalWorkspace export, so every App render threw and 1320 tests
failed; and the workspace-overview facet counts no longer include the
per-mount /hooks probe, which broke the exact facet-set assertion.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw2kfno0dl

* fix(cli): explicit folder trust alone runs project hooks

The startup gate demanded a stored approval even when the folder was
explicitly trusted, contradicting the documented either-or authority:
core's readProjectHooks accepts approval OR explicit trust, but the
pending flag short-circuited the accessor before trust was consulted.
A platform that pre-trusts its managed directories (folderTrust enabled
plus a TRUST_FOLDER/TRUST_PARENT entry) saw every project hook withheld
headless. Clear the pending gate for a trusted folder unless the exact
block was explicitly rejected — a recorded 'no' still wins over trust.

Verified headless on a real fixture: fresh home withholds; trust config
alone fires the hook; a rejected block stays withheld in the same
trusted folder. Gate unit tests cover pending/rejected/approved under
explicit trust.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(hooks): bind the approval decision to the rendered block hash end to end

The status cell now publishes pendingApprovalHash, the Web Shell card
echoes it with the decision, and both the daemon route and the ACP child
verify it: the daemon rejects a decision whose hash names a block other
than the current file (409 project_hooks_changed, nothing persisted),
and the child applies a hash-bound decision only to configs holding
that exact startup subject (projectHooksApprovalSubject), leaving any
config with a different block pending. Closes the cold-daemon re-read
window: an approval can no longer bind commands the user never saw.

Verified on a real daemon: render hash -> edit the hook file -> POST
with the rendered hash returns 409 and persists nothing; re-render ->
approve with the fresh hash persists and enables the hook; a real
browser session then fires it. Card tests 5/5 (including hash echo),
approve-control tests 2/2, gate tests 20/20, build + bundle clean.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(hooks): close the remaining approval-gate review findings

- Escape on the Web Shell trust card defers without recording a decision
  (TUI semantics): ToolApproval gains escapeDismisses/onDismiss, and the
  card hides until the block hash changes instead of persisting a
  rejection for a habitual keypress.
- The card's options are *_once kinds so the shared safe-default logic
  pre-selects the reject side; a stray Enter on the security gate can
  never grant hook execution. Decision-specific labels stay via the
  option-id i18n keys.
- The hooks-trust card reports into approvalOverlayActive, so the
  composer is dormant while the card owns the keyboard, exactly like a
  tool approval.
- Under command-line YOLO, replacing the hooks block drops the old
  approval authority: an edited block cannot execute on the previous
  block's approval while the gate is exempt.
- The trust-withheld listing surfaces the display-only block even when
  the empty execution block reads the gate as cleared (feature on,
  folder untrusted) — the state the copy exists for.

Tests: core config + hooks-listing 1055/1055, hooks reload 38/38,
ToolApproval + card 99/99 including Escape-defer and reject-default
regressions; build + bundle clean.

Co-authored-by: Qwen-Coder <[email protected]>

* chore: record merge provenance

The main merge (952e0633) refreshed the lint gate; this marker commit
carries the required co-author trailer for the batch.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(hooks): propagate trust authority to every gate re-application path

Review round at 2a00fb4d: the startup gate honored explicit trust, but
the auxiliary paths re-read only the approval store and re-pended hooks
that trust already authorized.

- applyProjectHooksApproval (the /hooks reload and YOLO-exit path) now
  applies the same either-or rule as startup: explicit trust clears a
  pending block; only an explicit rejection of the exact block wins.
- The startup gate mirrors Config.isExplicitlyTrustedFolder(): an
  IDE-reported trusted workspace counts even with the feature off.
- The headless trust warning stays quiet when the execution accessor
  actually serves the block (approved or trust-authorized), matching the
  TUI predicate; the test mock now carries workspace.settings.hooks.
- The live status cell and the cold-daemon fallback stop prompting when
  the hooks are not actually withheld (explicit trust / served block),
  so a trusted workspace never sees a card whose reject would stop
  running hooks.
- The card's Escape defer is keyed by workspace identity read at dismiss
  time (no reset effect can race it), clears on workspace switch, and a
  failed decision POST renders an explicit retry notice.
- The withheld /hooks listing filters non-event keys and null entries
  like the registry path; the YOLO replacement comment now states the
  fail-closed behavior instead of promising a re-apply that never runs.

Tests: daemon facade covers cold-daemon pending+hash, trusted no-prompt,
hash-match persist, 409 changed-block persists nothing, not-pending;
gate re-apply under trust; IDE trust with feature off; trusted reload
keeps hooks / untrusted edit withheld. cli 1879/1879, core 1055/1055,
web-shell card+ToolApproval 99/99. Real browser: trusted dir runs hooks
with no card and no approval record; untrusted dir card -> Escape defer
(nothing persisted) -> approve -> hook fires.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
2026-11-05 14:42:13 +08:00
2025-07-22 23:26:01 +08:00
2025-08-08 20:45:54 +08:00
2025-07-22 23:26:01 +08:00
2026-11-04 04:49:30 +00:00
2025-10-23 09:27:04 +08:00
2025-12-22 21:11:33 +08:00

npm version License Node.js Version Downloads

QwenLM%2Fqwen-code | Trendshift

The open-source AI coding agent for your terminal, editor, desktop, browser, and chat.

中文 | Deutsch | français | 日本語 | Русский | Português (Brasil) | 한국어

Qwen Code in the terminal, desktop app, browser, editor, and chat, with VS Code, Zed, JetBrains, Telegram, DingTalk, WeChat, and Feishu integrations

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, Web UI, SDKs, and chat integrations (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

Open a terminal in your project and start Qwen Code:

cd /path/to/your-project
qwen

Inside the session, run /auth to configure your provider and API key. Then try:

Explain this repository and show me where to start.

See the Authentication Guide and Settings Reference for detailed setup.

See the terminal interface

Qwen Code terminal interface

How to Use Qwen Code

Choose the interface that fits your workflow:

Interface Get started
Terminal Run qwen in your project — see Quick Start.
Desktop Download Qwen Code Desktop for macOS, Windows, and Linux.
Web Run qwen serve --open to open the Web UI in your browser. (experimental)
Editor Set up VS Code, Zed, or JetBrains.
Chat Connect Telegram, DingTalk, WeChat, or Feishu, then run qwen channel start.

For automation and custom integrations:

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())

Daemon Mode

Run Qwen Code as a long-lived service when you want browser, editor, SDK, and automation clients to share agent sessions without each starting a separate process. The daemon provides the built-in Web Shell and an HTTP + SSE API, supports multiple workspaces, and lets clients reconnect to active sessions.

Start it in the foreground and open the Web Shell:

qwen serve --open

The default address is http://127.0.0.1:4170. To run and manage it in the background instead:

qwen daemon start
qwen daemon status
qwen daemon logs -f
qwen daemon stop

By default, the daemon listens on loopback without bearer authentication; any local process that can reach it can use the full API, including code execution. On shared or untrusted hosts, set QWEN_SERVER_TOKEN and use --require-auth. See the Daemon guide for workspaces, authentication, remote access, and the API.

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) ✓ —

Qwen Code Evaluation

On SWE-bench Verified, seven Qwen Code versions scored 76.40%–77.80% on average using the same Qwen 3.7 Max model (500 tasks, three trials per version).

Average SWE-bench Verified scores by Qwen Code version: 0.0.14, 77.80%; 0.1.0, 76.73%; 0.5.0, 76.60%; 0.10.0, 76.47%; 0.15.0, 77.67%; 0.19.0, 76.40%; 0.22.0, 77.33%.

Full results and evaluation configuration →

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-audio-agent — Realtime full-duplex voice frontend for Qwen Code over ACP — talk hands-free with barge-in and a local wake word

  • 🦞 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

Privacy

See the Terms of Service and Privacy Notice, including Chrome extension and Browser Use data handling.

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.

Languages
TypeScript 76.5%
Java 11.5%
JavaScript 5.4%
Rust 5%
Python 0.5%
Other 0.9%