Skip to content
Agentic Control Plane

Qwen Code permissions, approval modes and hooks reference

David Crowe David Crowe · · Updated · 15 min read
qwen-code permissions hooks reference
Share X HN LinkedIn

Qwen Code has five approval modes: plan, default (labelled Ask Permissions), auto-edit, auto and yolo. The default is auto, where an LLM classifier decides which actions run; you set the mode with tools.approvalMode in settings.json or --approval-mode on the command line. Below that sit permissions.allow / ask / deny rules, an optional sandbox, and a hook system with PreToolUse and PostToolUse that can allow, ask, deny or rewrite any tool call.

Qwen Code is Alibaba’s open-source terminal coding agent, forked from Gemini CLI. Everything here comes from its repository docs and source on main as of 2026-11-07. For installing a hook that records every call, see the Qwen Code install guide; for how Qwen Code compares with other harnesses, see the Qwen Code control model.

Approval modes

The settings schema defines tools.approvalMode with default: ApprovalMode.AUTO. The settings reference describes each value:

Mode File edits Shell commands
plan Not run. “Analyze only” Not run
default (Ask Permissions) Prompt Prompt
auto-edit Auto-approved (edit, write_file, notebook_edit) Prompt
auto (default) Classifier decides Classifier decides
yolo Auto-approved Auto-approved

The approval mode doc notes that the mode once named Default is now called Ask Permissions; the config value "default" is kept for compatibility. That page is not consistent with itself about which mode Qwen Code starts in. The schema is the source of truth: auto.

Three ways to set it:

# Per run
qwen --approval-mode auto-edit
qwen --yolo                       # same as --approval-mode yolo; the two cannot be combined
{
  "tools": { "approvalMode": "default" }
}

In a session, Shift+Tab (Tab on Windows) cycles plan → default → auto-edit → auto → yolo. /approval-mode <mode> sets it directly, and /approval-mode yolo --project or --user writes the choice to that scope’s settings file. /plan enters plan mode and /plan exit restores the previous one.

What auto mode does

The auto mode doc describes an LLM classifier that reviews shell commands, network calls and edits outside the workspace. It runs on your configured fast model (/model --fast), or the main model if none is set. Most read-only operations and in-workspace edits skip it.

  • A block fails the call with Blocked by auto mode policy: <reason>.
  • If the classifier is unreachable, times out or returns bad output, that action falls back to a manual approval prompt. After two unavailable results in a row, later calls go straight to manual approval.
  • After three blocks in a row, the third falls back to the manual prompt.
  • permissions.deny runs before the classifier. Over-broad allow rules such as ["Bash"] are set aside while auto mode is on and restored on exit.

You can steer it under permissions.autoMode: hints.allow, softDeny and hardDeny lists (the older hints.deny is still read as softDeny), environment for natural-language context, and classifyAllShell to send every shell command through the classifier.

Settings files and precedence

From the settings reference, lowest to highest priority:

  1. Built-in defaults
  2. System defaults file: /etc/qwen-code/system-defaults.json (Linux), /Library/Application Support/QwenCode/system-defaults.json (macOS), C:\ProgramData\qwen-code\system-defaults.json (Windows)
  3. User settings: ~/.qwen/settings.json
  4. Project settings: .qwen/settings.json in the project root
  5. System settings: /etc/qwen-code/settings.json (Linux), /Library/Application Support/QwenCode/settings.json (macOS), C:\ProgramData\qwen-code\settings.json (Windows)
  6. Environment variables
  7. Command-line arguments

QWEN_CODE_SYSTEM_DEFAULTS_PATH and QWEN_CODE_SYSTEM_SETTINGS_PATH override the two system paths. Because the system settings file sits above user and project files, an administrator can pin values for every user on a machine.

Permission rules: allow, ask, deny

The permissions block uses the same rule grammar as Claude Code:

{
  "permissions": {
    "allow": ["Bash(git status)", "Read"],
    "ask":   ["Bash(git push *)"],
    "deny":  ["Read(./.env)", "Bash(rm -rf *)", "mcp__untrusted"]
  }
}

Rules from the settings reference:

  • Priority: deny > ask > allow > the approval mode. Rules are merged across user, project and system scopes.
  • Format: "ToolName" or "ToolName(specifier)". Bash(git *) matches commands starting with git at a word boundary, so not gitk.
  • Meta-categories: Read covers read_file, grep_search, glob and list_directory. Edit covers edit, write_file and notebook_edit.
  • Paths: // is absolute, ~/ is home, / is the project root, ./ or no prefix is the working directory.
  • Shell equivalents: file and network rules are also enforced against shell commands that do the same thing, such as cat .env.
  • Deny removes: a whole-tool deny takes a built-in tool out of the registry. MCP tools stay registered and are blocked at runtime.

For a single run, --allowed-tools "Shell(git status)" lets listed tools bypass the confirmation dialog, and --exclude-tools shell,write,edit removes built-ins entirely.

Deprecated keys still read

tools.allowed (use permissions.allow) and tools.exclude (use permissions.deny) are deprecated. tools.exclude is not migrated automatically and is still honoured at startup. tools.core, an allowlist for built-in tools, is marked deprecated and due for removal. Check all three when auditing an older install.

Folder trust

Folder trust is off by default. Turn it on in user settings:

{
  "security": { "folderTrust": { "enabled": true } }
}

Decisions are saved in ~/.qwen/trustedFolders.json. In an untrusted folder Qwen Code does not load the project’s .qwen/settings.json or .env files, and it prompts before every tool runs, “even if you have auto-acceptance enabled globally.”

Sandbox

The sandbox doc lists two separate mechanisms. Both are off by default.

Whole-CLI sandbox. Enable it with the environment variable, the flag or the setting; the variable wins over the other two:

QWEN_SANDBOX=docker qwen          # true | false | docker | podman | sandbox-exec
qwen -s -p "run the tests"        # or --sandbox=<provider>
{ "tools": { "sandbox": true } }

On macOS true picks Seatbelt (sandbox-exec) with the permissive-open profile. SEATBELT_PROFILE switches to permissive-closed, permissive-proxied, restrictive-open, restrictive-closed, restrictive-proxied, or a custom .qwen/sandbox-macos-<name>.sb. On Linux and Windows it needs Docker or Podman. The image comes from --sandbox-image, then QWEN_SANDBOX_IMAGE, then tools.sandboxImage, then the built-in default.

Linux tool-execution sandbox. tools.executionSandbox confines only shell and tool execution with bubblewrap, or Landlock as a fallback. It is read from user or system settings, not workspace settings:

{
  "tools": {
    "executionSandbox": { "backend": "auto", "filesystem": "workspace-write", "network": "closed" }
  }
}

qwen sandbox --verify checks it. The doc lists what it leaves alone: the CLI, model transport, auth and session storage stay on the host, reads stay broad, and hooks, extensions, MCP and several other features are disabled while it is on.

Hooks

Qwen Code has a full hook system. The hooks doc lists 23 events, including PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, SessionStart, SessionEnd, Stop, SubagentStart, SubagentStop, PreCompact, PermissionRequest and PermissionDenied.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "run_shell_command",
        "hooks": [{ "type": "command", "command": "/path/to/check.sh", "timeout": 10 }]
      }
    ]
  }
}

The contract:

  • Matcher: a regex on the runtime tool id (write_file, read_file, run_shell_command). "", "*" and ".*" match everything. Display aliases such as Bash and Write are also accepted.
  • Types: command, http and prompt for users. A fourth type, function, is internal to skills and the SDK.
  • Timeout: seconds, default 60 for command hooks. For compatibility, a command-hook value of 1000 or more is read as milliseconds.
  • stdin: session_id, transcript_path, cwd, hook_event_name, timestamp, permission_mode; PreToolUse adds tool_name, tool_input and tool_use_id, and PostToolUse adds tool_response.
  • Decision: hookSpecificOutput.permissionDecision of allow, deny or ask, with permissionDecisionReason. updatedInput replaces the whole tool input; additionalContext is appended to the result the model sees.
  • Exit codes: 0 is success, 2 blocks and sends stderr to the model, anything else is a non-blocking error.
  • Off switch: "disableAllHooks": true (schema default false, restart required). --safe-mode and --bare also turn hooks off.

Project-level hooks in .qwen/settings.json load only after you approve that exact configuration (stored in ~/.qwen/hookApprovals.json and invalidated by any edit) or after folder trust is granted. Non-interactive runs never prompt for this, so unapproved project hooks stay unloaded and qwen -p prints a warning. User-level hooks in ~/.qwen/settings.json have no such gate.

Extensions

Extensions are directories with a qwen-extension.json manifest under ~/.qwen/extensions, installed with qwen extensions install <source>. They contribute MCP servers, commands, skills, subagents, context files, workflows and settings. Gemini CLI extensions and Claude Code plugins are converted on install. The introduction does not list hooks or tool exclusions as manifest fields, so for blocking a call, hooks and permissions.deny are the tools.

Headless runs

qwen -p "..." (or piped stdin) runs non-interactively, per the headless doc. --output-format takes text, json or stream-json.

  • --yolo in CI auto-approves every tool call, including shell, write and edit, and does not turn on a sandbox. The doc’s suggested shape for an isolated runner is qwen -p "..." --yolo --max-session-turns N --max-wall-time 10m --output-format json, adding --sandbox on shared infrastructure.
  • --max-tool-calls and --max-wall-time abort with exit code 55; --max-session-turns exits 53.
  • A hook that answers ask in a headless run or a background subagent is treated as deny. Confirming an ask runs the tool once, and no approval mode, yolo included, can approve in its place.

The docs do not say what a built-in confirmation prompt does in a -p run, and the approval mode page contradicts the schema on the headless default. In CI, pass --approval-mode explicitly rather than relying on the default.

Where the model ends

  • auto uses a model’s judgment. The default mode puts a model call in front of shell commands. It falls back to a prompt when it fails, but its answers can differ run to run. For a fixed boundary, use default or auto-edit and write rules.
  • --yolo has no limits of its own. Only permissions.deny and hooks still apply, and it does not turn on a sandbox.
  • The sandboxes limit where a command runs, not which command. The Linux execution sandbox also disables hooks.
  • No record. Approval modes, rules and hooks decide each call but keep no log of what was allowed or refused. A PostToolUse hook that writes somewhere you control is the only built-in way to keep one.

ACP’s Qwen Code hook adds a log of every call plus rules that run in every approval mode, yolo included. A held call is blocked, and you approve it on the console or from the email, then retry. In a headless run Qwen Code itself turns an ask into a deny. The shared hook fails open if ACP is unreachable, unless you set ACP_FAIL_MODE=closed or write closed to ~/.acp/failmode. The Qwen Code install guide has the steps.

Frequently asked questions

What is the default approval mode in Qwen Code?

auto. The settings schema sets tools.approvalMode to auto, where an LLM classifier auto-approves actions it judges safe and blocks risky ones. The other values are plan, default (shown as Ask Permissions), auto-edit and yolo.

How do I turn on yolo mode in Qwen Code?

Pass –yolo or –approval-mode yolo on the command line, set tools.approvalMode to yolo in settings.json, or press Shift+Tab (Tab on Windows) to cycle to it in a session. –yolo does not enable a sandbox, so tools run with your own privileges.

How do I allow or deny a tool in Qwen Code?

Use the permissions block in ~/.qwen/settings.json or .qwen/settings.json: permissions.allow, permissions.ask and permissions.deny take rules such as Bash(git *), Read or mcp__server. deny beats ask, ask beats allow. tools.allowed and tools.exclude are deprecated.

Does Qwen Code have hooks?

Yes. A top-level hooks key in settings.json registers command, http or prompt hooks on 23 events, including PreToolUse and PostToolUse. A PreToolUse hook returns permissionDecision allow, ask or deny, or exits with code 2 to block.

What happens to a hook's ask in a headless Qwen Code run?

It becomes a deny. The hooks doc says that in headless (–prompt) runs and background subagents, ask falls back to deny, and no approval mode, including yolo, can approve in its place.

Where to read more

Running coding agents? ACP records each tool call its hook sees and makes the dangerous shell commands (a force-push, destructive SQL, curl | sh) wait for you. One command, free up to 5 agents:

macOS · Linux · WSL
curl -fsSL https://agenticcontrolplane.com/install.sh | bash
Windows · PowerShell
irm https://agenticcontrolplane.com/install.ps1 | iex

What the installer writes  ·  Getting started

Share X HN LinkedIn
Get the next data drop
What agents actually cost, new tool-surface captures, and the occasional incident post-mortem — sent when we publish something worth your inbox, not on a schedule. Unsubscribe anytime.
Share: Twitter LinkedIn
Related posts

← back to blog