Skip to main content
Guides

Permissions

Learn about Qoder CN CLI permission modes, rule configuration, and the tool-call approval flow.

Permission modes

A permission mode determines how Qoder CN CLI handles tool calls. Each mode sits at a different point on two axes: how much runs automatically, and how permissive it is with sensitive actions.
ModeWhen to use itBehavior
defaultRegular interactive use.Safe reads and internal actions run automatically. Sensitive actions ask for confirmation.
accept_editsDay-to-day coding tasks.Auto-approves safe file edits inside the working directory. Shell commands, external actions, and sensitive paths still go through the normal checks.
autoAutomated runs and Goal execution.Zero prompts. Safe reads and in-workspace edits are auto-approved. Risky actions are denied or handed to an AI classifier for judgment.
bypass_permissions (YOLO)Trusted local experiments only.Skips all approval prompts. Every tool call is auto-approved.
dont_askHeadless flows that must never prompt.Never asks. Anything that would have prompted is denied.

Plan mode

Plan is a standalone working state (not a permission policy) and can coexist with any of the permission modes above. Toggle it with the /plan command. While in Plan, Qoder CN CLI performs read-only exploration of the code and produces a plan; writes are restricted to the plan file. When you exit Plan, you can pick the next permission mode or move directly into Goal execution.

Goal mode

Goal is an autonomous execution state. Enter it with /goal set <goal> — the CLI automatically switches to auto mode and locks Shift+Tab so nothing interrupts the run. Goal can coexist with Plan (plan first, then execute). Exit with /goal clear or /goal pause.

Cycling through modes

In an interactive session, press Shift+Tab to cycle through the permission modes. Press Ctrl+Y to jump straight to YOLO.

Launch flags

Use --permission-mode to choose the default behavior for the current session:
qoderclicn --permission-mode default
qoderclicn --permission-mode accept_edits
qoderclicn --permission-mode plan           # Backward compatible; translates to default + entering the Plan working state
qoderclicn --permission-mode auto
qoderclicn --permission-mode bypass_permissions
qoderclicn --permission-mode yolo           # Equivalent to bypass_permissions
qoderclicn --permission-mode dont_ask

# Shortcuts
qoderclicn --yolo                         # Equivalent to --permission-mode bypass_permissions
qoderclicn --dangerously-skip-permissions # Same as above
Inside an interactive session, you can also press Ctrl+Y to switch to YOLO quickly. Multiple naming formats are supported (case-insensitive):
Canonical value (snake_case)camelCaseAliases
accept_editsacceptEdits
bypass_permissionsbypassPermissionsyolo, YOLO
dont_askdontAsk
Examples:
# The following are equivalent
qoderclicn --permission-mode bypass_permissions
qoderclicn --permission-mode bypassPermissions
qoderclicn --permission-mode yolo
qoderclicn --yolo
qoderclicn --dangerously-skip-permissions
Non-default modes only take effect inside trusted directories. If the current directory is not trusted, Qoder CN CLI falls back to default.

How permissions are decided

Qoder CN CLI runs a permission check before every tool call. The check has exactly three possible outcomes:
  • allow: run the tool immediately.
  • ask: require external confirmation before running.
  • deny: block this tool call.
Permissions apply to file reads and edits, Bash commands, web fetches, MCP tools, subagents, and other built-in tools.

Decision order

Qoder CN CLI evaluates permissions in a fixed order:
  1. Check deny rules first — any hit denies immediately.
  2. Run the tool's own safety checks (dangerous-command detection, sensitive-path detection, and so on).
  3. Check ask rules — any hit marks the call as requiring confirmation.
  4. Apply tool-level allow rules and the auto-allow behavior contributed by the current mode.
  5. If the result is still ask, the runtime decides how to consume it.
A broad allow rule does not mean everything runs silently — safety checks and ask rules take precedence.

How ask is resolved in each runtime

RuntimeWhere ask goesNotes
TUI (interactive terminal)In-terminal confirmation promptYou choose allow or deny in the terminal.
Headless (-p/--prompt)Auto-deniedNo human present, so ask becomes deny.
SDK (stdio protocol)Sends a canUseTool callback to the hostThe host program decides allow or deny.
ACP (IDE integration)Sends a requestPermission RPC to the IDEThe IDE prompts or decides automatically.
In headless mode (-p/--prompt), you can combine a permission mode with the flag:
# Headless + accept_edits: file edits auto-pass, Bash is denied
qoderclicn -p "refactor the utils module" --permission-mode accept_edits

# Headless + bypass_permissions: everything is allowed (trusted scenarios only)
qoderclicn -p "run the migration" --yolo

# Headless + precise allow rules: only specific tools are allowed
qoderclicn -p "check status" --allowed-tools 'Read,Bash(git status)'

Permission configuration

Rule sources (8-layer priority)

Rules are merged from multiple sources, from lowest to highest priority:
LevelSourceDescription
1userSettings~/.qoder-cn/settings.json (user-global).
2projectSettings/.qoder/settings.json (project-scoped, shared with the team).
3localSettings/.qoder/settings.local.json (personal to this machine — add it to .gitignore).
4flagSettingsThe file passed via the --settings CLI argument.
5cliArgThe --allowed-tools and --disallowed-tools command-line arguments.
6command/allow and /deny commands entered during a session.
7sessionRuntime rules created ad hoc ("allow for this session" in a prompt).
Rules from higher-priority sources override lower-priority ones. If your organization policy enables allowManagedPermissionRulesOnly, only the policy-managed rules are used. How to configure each source:
  • Levels 1-3 (settings files): write permissions.allow / permissions.deny / permissions.ask arrays into the JSON file at the corresponding path. settings.local.json is the recommended place for personal per-machine approval rules and should be added to .gitignore.
  • Level 4 (flagSettings): qoderclicn --settings ./custom-settings.json points to an additional settings file. The file uses the same format as a standard settings.json.
  • Level 5 (cliArg): configure with the --permission-mode, --allowed-tools, --disallowed-tools, --tools, and similar command-line flags. Only affects the current session.
  • Level 6 (command): type /allow Bash(npm test) or /deny WebFetch during a session; the rule is persisted to settings.local.json.
  • Level 7 (session): choosing "allow for this session" in a prompt produces a temporary rule that expires when the process exits.

Mode configuration

Set the default mode — configure general.defaultPermissionMode in settings:
{
  "general": {
    "defaultPermissionMode": "accept_edits"
  }
}
Disable YOLO mode — organization administrators can prevent users from entering bypass_permissions mode with the following configuration:
{
  "security": {
    "disableYoloMode": true
  }
}
Once set, --yolo, --permission-mode bypass_permissions, and Ctrl+Y are all unavailable, and the Shift+Tab cycle skips the mode. A subagent that declares bypass is downgraded to acceptEdits. Disable Plan mode — if you do not need the Plan workflow, turn it off:
{
  "general": {
    "plan": {
      "enabled": false
    }
  }
}
Once set, the /plan command is unavailable, --permission-mode plan falls back to default, and the EnterPlanMode / ExitPlanMode tools are not registered. Auto mode classifier configuration — you can use natural-language rules to steer the AI classifier's judgment:
{
  "autoMode": {
    "allow": [
      "running npm/yarn/pnpm scripts defined in package.json",
      "creating or editing test files"
    ],
    "soft_deny": [
      "deleting files outside the test directory",
      "modifying CI/CD configuration"
    ],
    "environment": [
      "This is a Node.js monorepo with pnpm workspaces",
      "The project uses Vitest for testing"
    ]
  }
}
FieldPurpose
allowDescriptions of operations the classifier should tend to approve automatically.
soft_denyDescriptions of operations the classifier should tend to deny.
environmentEnvironment context provided to the classifier.
These rules are soft guidance — they are injected into the classifier prompt as a reference, and the final decision still comes from the AI classifier. For security reasons, autoMode configuration is only read from trusted sources (user-global settings and localSettings). Project settings are excluded to prevent malicious privilege escalation.

Permission rule configuration

Rules are grouped into allow, ask, and deny:
{
  "permissions": {
    "allow": [
      "Read(/src/**)",
      "Edit(/src/**)",
      "Bash(npm run test:*)"
    ],
    "ask": [
      "Bash(npm publish:*)",
      "WebFetch"
    ],
    "deny": [
      "Read(*.pem)",
      "Bash(rm -rf:*)"
    ]
  }
}

Rule syntax

FormMeaning
ToolNameApplies to the whole tool.
ToolName(content)Applies to a specific path, command, agent type, or any other content the tool supports.
&#42;Matches every tool.
Use canonical tool names: Read, Edit, Write, Bash, Grep, Glob, WebFetch, WebSearch, and Agent, as well as MCP tool names like mcp__github__create_issue. Escape parentheses inside the content when they appear:
{
  "permissions": {
    "allow": [
      "Bash(python -c \"print\\(1\\)\")"
    ]
  }
}
ToolName(*) is equivalent to ToolName (a tool-level rule).

Command-line overrides

qoderclicn --allowed-tools 'Read,Grep,Bash(git status)'
qoderclicn --disallowed-tools 'Bash(rm -rf:*),mcp__github__delete_repo'
qoderclicn --tools 'Read,Grep,Edit'
--allowed-tools and --disallowed-tools use the same syntax as settings rules. --tools restricts the set of built-in tools available for this run — anything not listed is denied.

Trusted directories

Qoder CN CLI treats the current working directory (CWD) at startup as the primary trusted directory. Inside a trusted directory:
  • File reads are allowed by default.
  • File writes can be auto-approved in accept_edits and auto modes.
  • Non-default permission modes (auto, bypass, and so on) are able to take effect.
If the current directory is not trusted, Qoder CN CLI forces the mode back to default.

Extending trust

Add extra trusted working directories with --add-dir, the /add-dir command, or permissions.additionalDirectories:
qoderclicn --add-dir ../shared
{
  "permissions": {
    "additionalDirectories": ["../shared"]
  }
}
You can also configure permissions.trustDirectories in your global settings to permanently trust frequently-used directories.

Protected paths

Some paths are protected because editing them can change execution behavior, credentials, or tool behavior. Examples include .git, .vscode, .idea, .husky, most .qoder configuration files, shell startup files like .bashrc and .zshrc, Git configuration, .mcp.json, and .ripgreprc. In regular interactive modes these paths require explicit approval; in auto mode they are denied.

File access rules

Read rules with a path scope use Read(...). Write rules with a path scope use Edit(...); the write scope covers file-write checks for Edit, Write, and NotebookEdit. An Edit(...) allow rule on a path also implicitly allows reads on the same path. File rules use gitignore-style matching.
PatternMeaning
/src/&#42;&#42;A path rooted at the rule source's root directory. In project or local settings, that is the project root; in user settings, it is the home directory.
~/Documents/&#42;&#42;A path rooted at the home directory.
//tmp/data/&#42;&#42;An absolute path starting at system /. Absolute paths require a double slash.
&#42;.secretA rootless filename pattern that matches anywhere.
Example:
{
  "permissions": {
    "allow": [
      "Read(/src/**)",
      "Edit(/src/**)",
      "Read(~/Documents/specs/**)"
    ],
    "ask": [
      "Edit(/package.json)",
      "Read(~/Downloads/**)"
    ],
    "deny": [
      "Read(*.pem)",
      "Edit(/.git/**)",
      "Edit(//etc/**)"
    ]
  }
}

Bash rules

Bash(...) rules can match an exact command, a command prefix, or a glob pattern.
RuleWhat it matches
Bash(npm run build)Exact match for npm run build.
Bash(npm run test:&#42;)Matches npm run test and any command starting with npm run test.
Bash(git log &#42;)Glob-style wildcard matching.
Bash(git status)Exact match for git status.
Example:
{
  "permissions": {
    "allow": [
      "Bash(git status)",
      "Bash(git log:*)",
      "Bash(npm run test:*)"
    ],
    "ask": [
      "Bash(npm publish:*)",
      "Bash(git push:*)"
    ],
    "deny": [
      "Bash(rm -rf:*)",
      "Bash(sudo:*)"
    ]
  }
}
Shell matching is conservative:
  • deny and ask rules see through common wrappers and environment-variable prefixes, so Bash(rm -rf:*) still blocks a wrapped destructive command.
  • Prefix and wildcard allow rules do not silently approve compound commands unless every top-level command fragment can be allowed on its own.
  • Some shell commands that can be proven read-only may be auto-allowed after deny, ask, and path checks pass.
  • Dangerous commands (such as destructive deletes or force pushes) may still require confirmation even when a broad allow rule exists. In auto mode, dangerous shell commands are denied.
Avoid broad rules like Bash or Bash(*) unless you fully trust the current session.

Web and MCP rules

Web tools can be controlled with tool-level rules. Use ask if every web fetch should require confirmation, or deny to disable network access for a session or project.
{
  "permissions": {
    "ask": [
      "WebFetch"
    ],
    "deny": [
      "WebSearch"
    ]
  }
}
MCP tools use fully-qualified names:
mcp__<server>__<tool>
Supported MCP patterns include:
RuleMeaning
mcp&#95;&#95;github&#95;&#95;create_issueA single MCP tool.
mcp&#95;&#95;github&#95;&#95;&#42;Every tool provided by the github MCP server.
mcp&#95;&#95;githubEvery tool provided by the github MCP server.
mcp&#95;&#95;&#42;Every MCP tool.
Example:
{
  "permissions": {
    "allow": [
      "mcp__context7__*"
    ],
    "ask": [
      "mcp__github__create_issue"
    ]
  }
}
An MCP server's own configuration can also grant automatic approval for its tools via alwaysAllow. To limit a single run to specific MCP servers, use --allowed-mcp-server-names.
qoderclicn --allowed-mcp-server-names context7,github

Hooks and permissions

The Qoder CN CLI Hooks system has two injection points in the permission decision chain, letting custom scripts influence whether a tool call is allowed or denied.

Hook events that affect permissions

Hook event

When it fires

Permission impact

PreToolUse

Before the tool runs (during permission checks).

Can return permissionDecision: "allow" | "deny" | "ask" to override the permission pipeline outcome.

PermissionRequest

After the pipeline produces ask, before the prompt is shown.

Can return decision.behavior of allow or deny in place of user interaction.

Other hook events (PostToolUse, SessionStart, Stop, and so on) do not take part in permission decisions.

PreToolUse hook

Fires before a tool runs. The hook script inspects the tool name and arguments and returns a permission decision:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python ./scripts/check-bash-command.py"
          }
        ]
      }
    ]
  }
}
The hook script receives JSON on stdin (including tool_name, tool_input, and session_id) and writes JSON to stdout:
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Command blocked by security policy"
  }
}
Possible permissionDecision values:
  • "allow": skip the permission pipeline and approve directly.
  • "deny": skip the pipeline and deny directly.
  • "ask": continue through the normal pipeline (the default behavior).

PermissionRequest hook

Fires after the pipeline outputs ask but before the prompt or callback. This is a good fit for automated approval systems or external notifications such as Slack or email alerts.
{
  "hooks": {
    "PermissionRequest": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "node ./scripts/auto-approve-safe-ops.js"
          }
        ]
      }
    ]
  }
}
Output format:
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": {},
      "updatedPermissions": []
    }
  }
}

Hook priority vs. permission modes

Hook permission decisions outrank permission modes. Even in bypass_permissions mode, a PreToolUse hook that returns deny still blocks execution. This gives organizations an unbypassable safety net. Execution order:
  1. PreToolUse hook — a returned allow or deny short-circuits the rest.
  2. Permission pipeline (rules + mode + safety checks).
  3. If the result is ask, the PermissionRequest hook fires — a returned allow or deny short-circuits.
  4. The runtime consumes the remaining ask (prompt, deny, or callback).