Skip to main content

Permission Modes

Permission modes determine how Qoder handles tool calls — each mode balances automation and security differently.
ModeBest forBehavior
defaultNormal interactive useSafe reads and internal actions run automatically; sensitive actions require confirmation.
accept_editsRoutine coding tasksAutomatically approves safe file edits inside working directories. Shell commands, external actions, and sensitive paths still go through normal checks.
autoAutonomous runs, Goal executionZero prompts. Safe reads and workspace edits are auto-approved; risky actions are denied or evaluated by the AI classifier.
bypass_permissions (YOLO)Trusted local experiments onlySkips all approval prompts. All tool calls are allowed automatically.
dont_askHeadless flows that must not promptNever prompts. Any action that would normally ask is denied instead.

Plan Mode

Plan is an independent work state (not a permission policy) that can coexist with any permission mode above. Toggle it with the /plan command. While active, Qoder explores code in read-only mode and outputs proposals; writes are restricted to plan files. On exit you can choose a follow-up permission mode or start a Goal execution.

Goal Mode

Goal is an autonomous execution state. Enter via /goal set <objective> — this automatically switches to auto mode and locks Shift+Tab switching to ensure zero-interruption execution. Goal can coexist with Plan (plan first, execute second). Exit with /goal clear or /goal pause.

Mode Cycling

In interactive sessions, press Shift+Tab to cycle through all permission modes. Press Ctrl+Y to jump directly to YOLO mode.

Startup Parameters

Use --permission-mode to set the default behavior for the current session:
qodercli --permission-mode default
qodercli --permission-mode accept_edits
qodercli --permission-mode plan           # Legacy compat: translates to default + enters Plan work state
qodercli --permission-mode auto
qodercli --permission-mode bypass_permissions
qodercli --permission-mode yolo           # Equivalent to bypass_permissions
qodercli --permission-mode dont_ask

# Shortcuts
qodercli --yolo                         # Equivalent to --permission-mode bypass_permissions
qodercli --dangerously-skip-permissions # Same as above
In interactive sessions, you can also press Ctrl+Y to quickly switch to YOLO mode. Multiple naming formats are supported (case-insensitive):
Standard (snake_case)camelCaseAlias
accept_editsacceptEdits
bypass_permissionsbypassPermissionsyolo, YOLO
dont_askdontAsk
Example:
# The following are equivalent
qodercli --permission-mode bypass_permissions
qodercli --permission-mode bypassPermissions
qodercli --permission-mode yolo
qodercli --yolo
qodercli --dangerously-skip-permissions
Non-default modes only take effect in trusted directories. If the current directory is not trusted, Qoder falls back to default.

How Decisions Are Made

Qoder checks permissions before every tool call. The result is always one of three outcomes:
  • allow: Execute the tool immediately.
  • ask: Requires external confirmation before execution.
  • deny: Block the tool call.
Permissions apply to file reads and edits, Bash commands, web fetches, MCP tools, subagents, and other built-in tools.

Decision Order

Qoder evaluates permissions in a fixed order:
  1. Check deny rules first — if matched, deny immediately.
  2. Tool’s own safety checks (e.g., dangerous command detection, sensitive path detection).
  3. ask rules — if matched, mark as requiring confirmation.
  4. Tool-level allow rules and mode-based auto-allow behavior.
  5. If the final result is still ask, the runtime environment determines how to consume it.
Broad allow rules do not mean all actions execute silently — safety checks and ask rules have higher priority.

How ask Is Consumed in Different Environments

Environmentask outcomeNotes
TUI (interactive terminal)Confirmation promptUser selects allow/deny in the terminal.
Headless (-p/--prompt)Auto-denyNo interaction available; ask becomes deny.
SDK (stdio protocol)Sends canUseTool callback to hostHost program decides allow/deny.
ACP (IDE integration)Sends requestPermission RPC to IDEIDE prompts or auto-decides.
Headless mode (-p/--prompt) can be combined with permission modes:
# Headless + accept_edits: file edits auto-approved, Bash denied
qodercli -p "refactor the utils module" --permission-mode accept_edits

# Headless + bypass_permissions: all allowed (trusted scenarios only)
qodercli -p "run the migration" --yolo

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

Permission Configuration

Configuration Sources (8-Layer Priority)

Rules merge from multiple sources, from lowest to highest priority:
LayerSourceDescription
1userSettings~/.qoder/settings.json (user global)
2projectSettings<project>/.qoder/settings.json (project-level, team-shared)
3localSettings<project>/.qoder/settings.local.json (machine-local, add to .gitignore)
4flagSettings--settings <path> CLI argument specifying an additional file
5policySettingsOrganization policy (managed, highest persistent source)
6cliArg--allowed-tools / --disallowed-tools CLI arguments
7command/allow, /deny in-session commands
8sessionRuntime temporary rules (“Allow for this session” in prompts)
Higher-priority sources override lower ones. If organization policy enables allowManagedPermissionRulesOnly, only policy-managed rules are used. How each source is configured:
  • Layers 1-3 (settings files): Write permissions.allow / permissions.deny / permissions.ask arrays in the corresponding JSON file. settings.local.json is ideal for machine-local approval rules; add it to .gitignore.
  • Layer 4 (flagSettings): qodercli --settings ./custom-settings.json specifies an additional settings file. Same format as standard settings.json.
  • Layer 5 (policySettings): Distributed by organization admins via the policy system; cannot be overridden locally.
  • Layer 6 (cliArg): Configured via --permission-mode, --allowed-tools, --disallowed-tools, --tools CLI arguments; applies to current session only.
  • Layer 7 (command): Type /allow Bash(npm test) or /deny WebFetch in-session; persisted to settings.local.json.
  • Layer 8 (session): Temporary rules from selecting “Allow for this session” in prompts; lost when the process exits.

Mode Configuration

Set default mode — configure general.defaultPermissionMode in settings:
{
  "general": {
    "defaultPermissionMode": "accept_edits"
  }
}
Disable YOLO mode — organization admins can prevent users from entering bypass_permissions mode:
{
  "security": {
    "disableYoloMode": true
  }
}
When set, --yolo, --permission-mode bypass_permissions, and Ctrl+Y are all disabled, and the Shift+Tab cycle skips this mode. Sub-agents declaring bypass are also downgraded to acceptEdits. Disable Plan mode — if the Plan workflow is not needed:
{
  "general": {
    "plan": {
      "enabled": false
    }
  }
}
When set, the /plan command is unavailable, --permission-mode plan falls back to default, and EnterPlanMode/ExitPlanMode tools are not registered. Auto mode classifier configuration — guide the AI classifier’s decisions with natural language rules:
{
  "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
allowOperation descriptions the classifier tends to auto-approve
soft_denyOperation descriptions the classifier tends to deny
environmentEnvironment context provided to the classifier
These rules are soft guidance — injected into the classifier prompt as reference; the final decision is still made by the AI classifier. For security, 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 under 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 entire tool.
ToolName(content)Applies to a specific path, command, agent type, or other tool-specific value.
*Matches all tools.
Use canonical tool names: Read, Edit, Write, Bash, Grep, Glob, WebFetch, WebSearch, Agent, and MCP names like mcp__github__create_issue. If the content contains parentheses, escape them:
{
  "permissions": {
    "allow": [
      "Bash(python -c \"print\\(1\\)\")"
    ]
  }
}
ToolName(*) is equivalent to ToolName (tool-level rule).

Command-Line Overrides

qodercli --allowed-tools 'Read,Grep,Bash(git status)'
qodercli --disallowed-tools 'Bash(rm -rf:*),mcp__github__delete_repo'
qodercli --tools 'Read,Grep,Edit'
--allowed-tools and --disallowed-tools use the same rule syntax as settings. --tools restricts the available built-in tool set for the current run (unlisted tools are denied).

Trust Directories

Qoder treats the startup current working directory (CWD) as the main trust directory. Within trusted directories:
  • File reads are allowed by default
  • File writes can be auto-approved in accept_edits and auto modes
  • Non-default permission modes (auto, bypass, etc.) are allowed to take effect
If the current directory is not trusted, Qoder forces a fallback to default mode.

Extending Trust

Add additional trusted working directories via --add-dir, the /add-dir command, or permissions.additionalDirectories:
qodercli --add-dir ../shared
{
  "permissions": {
    "additionalDirectories": ["../shared"]
  }
}
You can also configure permissions.trustDirectories in 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/.zshrc, Git config, .mcp.json, and .ripgreprc. In normal interactive modes these paths require explicit approval; in auto mode they are denied.

File Access Rules

Path-scoped read rules use Read(...). Path-scoped write rules use Edit(...); they cover file editing and writing checks for Edit, Write, and NotebookEdit. An Edit(...) allow rule also implies read permission for the same path. File rules use gitignore-style matching.
PatternMeaning
/src/**Rooted at the rule source’s root directory. In project/local settings, relative to project root; in user settings, relative to home directory.
~/Documents/**Home-directory-based path.
//tmp/data/**Absolute path from system /. Use double slash for absolute paths.
*.secretRootless filename pattern that matches at any location.
Examples:
{
  "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 exact commands, command prefixes, or wildcard patterns.
RuleMatches
Bash(npm run build)Exactly npm run build.
Bash(npm run test:*)npm run test and commands beginning with npm run test .
Bash(git log *)Glob-style wildcard matching.
Bash(git status)Exactly git status.
Examples:
{
  "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 catches wrapped destructive commands.
  • Prefix and wildcard allow rules do not silently approve compound commands unless every top-level command segment is independently allowed.
  • Some provably read-only shell commands can be automatically allowed after deny, ask, and path checks.
  • Dangerous commands (destructive deletes, force pushes) can still force confirmation even with broad allow rules. In auto mode, dangerous shell commands are denied.
Avoid broad rules like Bash or Bash(*) unless you fully trust the session.

Web and MCP Rules

Web tools can be controlled at the tool level. Use ask when every web fetch should require confirmation, or deny when web access should be blocked 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__github__create_issueA single MCP tool.
mcp__github__*All tools from the github MCP server.
mcp__githubAll tools from the github MCP server.
mcp__*All MCP tools.
Example:
{
  "permissions": {
    "allow": [
      "mcp__context7__*"
    ],
    "ask": [
      "mcp__github__create_issue"
    ]
  }
}
MCP server configs can also set alwaysAllow for tools from that server. To enable only selected MCP servers for a run, use --allowed-mcp-server-names.
qodercli --allowed-mcp-server-names context7,github

Hooks and Permissions

Qoder’s Hook system has two injection points in the permission decision pipeline, allowing custom scripts to influence allow/deny behavior.

Hook Events That Affect Permissions

Hook EventTriggerPermission Impact
PreToolUseBefore tool execution (permission check phase)Can return `permissionDecision: “allow""deny""ask”` to directly override the pipeline result
PermissionRequestAfter pipeline produces ask, before promptCan return a decision object with behavior: "allow" or behavior: "deny" to replace user interaction
Other hook events (PostToolUse, SessionStart, Stop, etc.) do not participate in permission decisions.

PreToolUse Hook

Triggered before tool execution. The hook script can inspect the tool name and parameters, returning a permission decision:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python ./scripts/check-bash-command.py"
          }
        ]
      }
    ]
  }
}
Hook scripts receive JSON input via stdin (containing tool_name, tool_input, session_id, etc.) and output JSON results via stdout:
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Command blocked by security policy"
  }
}
permissionDecision values:
  • "allow": Skip permission pipeline, approve directly
  • "deny": Skip permission pipeline, deny directly
  • "ask": Continue through normal permission pipeline (default behavior)

PermissionRequest Hook

Triggered after the permission pipeline produces ask, before the prompt/callback. Suitable for automated approval systems or external notifications (e.g., Slack/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 have higher priority than permission modes — even in bypass_permissions mode, a PreToolUse hook returning deny will still block execution. This provides an unbypassable interception capability for organization-level security policies. Execution order:
  1. Hook PreToolUse → if returns allow/deny, short-circuit
  2. Permission pipeline (rules + mode + safety checks)
  3. If result is ask → Hook PermissionRequest → if returns allow/deny, short-circuit
  4. Finally, the runtime environment consumes ask (prompt/deny/callback)