OpenCode Permissions Reference
Updated by Alex Sorokoletov
Independent reference, not affiliated with Anomaly. Tables are generated from the OpenCode source at v1.18.33 (v1.18.33). Official documentation: opencode.ai.
OpenCode's permission config maps each tool key, such as edit, bash or webfetch, to allow, ask or deny, for the whole tool or per pattern. Rules are checked in order and the last match wins; a request matching nothing is asked. By default everything is allowed, except that .env reads, outside directories and repeated identical calls ask first.
How do OpenCode permissions work?#
Each tool call becomes a request: a permission key plus one or more patterns, such as the file path for edit or the command text for bash. OpenCode lays out one ordered rule list (built-in defaults, then the agent's built-in rules, then your top-level permission, then that agent's own permission block) and, for each pattern, takes the last rule whose key and pattern both match. If any pattern resolves to deny the call fails; if any resolves to ask you are prompted; otherwise it runs.
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"edit": "ask",
"bash": {
"*": "ask",
"git status *": "allow",
"git push *": "deny"
},
"webfetch": "deny"
}
}
Key order in your file is preserved, so put the broad "*" pattern first and exceptions after it. "permission": "ask" on its own is shorthand for { "*": "ask" }. A tool whose last rule for pattern * is deny is removed from the model's tool list rather than offered and refused. The legacy tools map still works ("write": false becomes edit: deny), but permission wins where both set the same tool.
Which permission keys exist?#
| Key | Accepts | Pattern is matched against | Default (build agent) |
|---|---|---|---|
read | action or {pattern: action} | file path relative to the worktree | *: allow; *.env: ask; *.env.*: ask; *.env.example: allow |
edit | action or {pattern: action} | file path relative to the worktree (edit, write and apply_patch) | allow (from `*`) |
glob | action or {pattern: action} | the glob pattern | allow (from `*`) |
grep | action or {pattern: action} | the search regex | allow (from `*`) |
list | action or {pattern: action} | no tool asks for this key in this release | allow (from `*`) |
bash | action or {pattern: action} | each command in the parsed command line | allow (from `*`) |
task | action or {pattern: action} | the subagent name | allow (from `*`) |
external_directory | action or {pattern: action} | directory outside the worktree, as `<dir>/*` | *: ask; <data>/tool-output/*, <tmp>/*, <skill dirs>/*, <reference dirs>/*: allow |
todowrite | action only | always `*` | allow (from `*`) |
question | action only | whether the agent may ask you a question | deny |
webfetch | action only | the URL | allow (from `*`) |
websearch | action only | the search query | allow (from `*`) |
lsp | action or {pattern: action} | always `*` | allow (from `*`) |
doom_loop | action only | the tool name, after 3 identical calls in a row | ask |
skill | action or {pattern: action} | the skill name | allow (from `*`) |
<server>_<tool> | action or {pattern: action} | always `*`; one key per MCP tool | allow (from `*`) |
Keys marked "action only" take allow, ask or deny and no pattern map. MCP tools have no shared key: each is checked under its own <server>_<tool> name, so "github_*": "ask" covers every tool of a server named github. The question, plan_enter and plan_exit keys control the ask-the-user and plan-mode tools.
How do bash patterns match?#
* matches any run of characters, including spaces and /, and ? matches one character; everything else is literal and the pattern must match the whole value. A pattern ending in * also matches the bare command, so git status * covers git status too. Backslashes are treated as /, and on Windows matching ignores case. A leading ~/ or $HOME in a pattern expands to your home directory.
OpenCode parses the command line and checks every command in it separately, using that command's full text, so npm test && git push needs both npm test and git push to be allowed. cd, pushd, popd and their PowerShell forms are not checked. For file commands (rm, cp, mv, mkdir, touch, chmod, chown, cat, cd and their PowerShell equivalents), arguments that point outside the project raise an external_directory request for their directories before the bash check.
Choosing "Allow always" on a bash prompt saves a prefix rule built from a table of how many words make up each command's name:
| Command approved | Rule "Always" adds |
|---|---|
git checkout main | git checkout * |
npm run dev | npm run dev * |
npm install react | npm install * |
rm -rf dist | rm * |
python -m venv env | python -m * |
docker compose up -d | docker compose up * |
ls -la | ls * |
What are the default OpenCode permissions?#
| Permission | Pattern | Action |
|---|---|---|
* | * | allow |
doom_loop | * | ask |
external_directory | * | ask |
external_directory | <data>/tool-output/*, <tmp>/*, <skill dirs>/*, <reference dirs>/* | allow |
question | * | deny |
plan_enter | * | deny |
plan_exit | * | deny |
read | * | allow |
read | *.env | ask |
read | *.env.* | ask |
read | *.env.example | allow |
<data> is OpenCode's data directory (~/.local/share/opencode by default) and <tmp> its folder in the system temp directory. Every agent also allows external_directory for <data>/tool-output/*, where truncated tool output is stored, unless your config denies that pattern.
How do I set permissions per agent?#
Put a permission block inside the agent's entry. Its rules are appended after the top-level ones, so for that agent they win:
{
"permission": { "bash": "ask" },
"agent": {
"build": { "permission": { "bash": { "*": "ask", "npm test *": "allow" } } },
"plan": { "permission": { "webfetch": "deny" } }
}
}
The built-in agents add these rules after the defaults and before your config:
| Agent | Mode | Hidden | Rules added after the defaults |
|---|---|---|---|
build | primary | no | question: allow; plan_enter: allow |
plan | primary | no | question: allow; plan_exit: allow; task general: deny; external_directory <data>/plans/*: allow; edit: deny; edit .opencode/plans/*.md: allow; edit <data>/plans/*.md: allow |
general | subagent | no | todowrite: deny |
explore | subagent | no | *: deny; grep: allow; glob: allow; list: allow; bash: allow; webfetch: allow; websearch: allow; read: allow; external_directory: ask; external_directory <data>/tool-output/*, <tmp>/*, <skill dirs>/*, <reference dirs>/*: allow |
compaction | primary | yes | *: deny |
title | primary | yes | *: deny |
summary | primary | yes | *: deny |
A subagent started through the task tool keeps the parent session's deny rules and external_directory rules, and is denied todowrite and task unless its own configuration mentions them.
What does "ask" do?#
| Command | An ask rule, by default | With --auto |
|---|---|---|
opencode | prompts you: Allow once, Allow always or Reject | answered "once" automatically; also toggled from the command palette ("Enable auto-approve permissions") |
opencode run | rejected automatically ("permission requested: ...; auto-rejecting") | answered "once" automatically |
"Allow once" approves this call. "Allow always" approves it and adds allow rules for the request's patterns (for bash, the prefix patterns above), which also releases other pending requests in the session that those rules now cover; the rules are held in memory and never written to config. "Reject" fails the call and rejects every other pending request in the same session; rejecting with a message sends that message to the model as feedback. deny rules are never prompted, so --auto cannot override them.
doom_loop fires when the model makes the same tool call with identical input three times in a row. OPENCODE_PERMISSION accepts a JSON object that is merged into permission after every config file.
How are OpenCode 2 permissions different?#
OpenCode 2 replaces the permission object with a permissions array of rules, each with action, resource and effect (allow, deny or ask), evaluated with the same last-match-wins rule. It also renames actions: bash becomes shell and task becomes subagent. OpenCode 1.x refuses a config that contains permissions, at the top level or inside an agent, with "V2 permissions are not supported by OpenCode V1". The OpenCode 2 permissions docs cover the new schema.
Frequently asked questions#
How do OpenCode permissions work?#
Each tool call is checked against an ordered list of rules: defaults, the agent's built-in rules, your permission block, then the agent's own permission. The last rule whose tool key and pattern both match decides: allow runs the call, ask prompts you, deny fails it. A call that matches no rule is asked.
How do I allow all permissions in OpenCode?#
Set "permission": "allow", which becomes { "*": "allow" }, or start OpenCode with --auto, which answers every prompt with "Allow once". In the TUI the command palette entry "Enable auto-approve permissions" toggles the same mode mid-session. Neither overrides a deny rule, and the default ask rules for .env files and outside directories stop prompting under --auto.
Is there an OpenCode --dangerously-skip-permissions flag?#
Yes, as a hidden alias. opencode and opencode run accept --auto, described as "auto-approve permissions that are not explicitly denied (dangerous!)", and treat --yolo and --dangerously-skip-permissions the same way. Without --auto, opencode run rejects every permission prompt automatically, so non-interactive runs need either --auto or explicit allow rules.
How do I set permissions for a specific OpenCode agent?#
Add permission inside agent.<name> in opencode.json, for example "agent": { "plan": { "permission": { "bash": "deny" } } }. Agent rules are appended after the top-level permission, so they win for that agent only. Markdown agents in .opencode/agent/ accept the same permission field in their frontmatter.
How do I let OpenCode access directories outside the project?#
Allow them under external_directory, which defaults to ask: "external_directory": { "~/projects/shared/*": "allow" }. The pattern is matched against the outside directory followed by /*, and ~/ expands to your home directory. read, edit, write, apply_patch, glob, grep and lsp targets outside the worktree, and bash file arguments pointing outside it, all go through this key before their own check.
What changed in OpenCode 2 permissions?#
OpenCode 2 uses a permissions array of { "action", "resource", "effect" } rules instead of the permission object, renames bash to shell and task to subagent, and keeps last-match-wins ordering. The two formats are not interchangeable: OpenCode 1.x rejects a config containing permissions, and the v2 docs tell you not to use permission, bash or task.
Sources
- OpenCode official documentation
- OpenCode release notes
packages/core/src/v1/config/permission.ts: permission config schema (keys, actions) (at v1.18.33)packages/opencode/src/agent/agent.ts: built-in agents and the default permission ruleset (at v1.18.33)packages/opencode/src/tool/read.ts(at v1.18.33)packages/opencode/src/tool/write.ts(at v1.18.33)packages/opencode/src/tool/glob.ts(at v1.18.33)packages/opencode/src/tool/grep.ts(at v1.18.33)packages/opencode/src/tool/shell.ts(at v1.18.33)packages/opencode/src/tool/registry.ts(at v1.18.33)packages/opencode/src/tool/external-directory.ts(at v1.18.33)packages/opencode/src/tool/todo.ts(at v1.18.33)packages/opencode/src/tool/webfetch.ts(at v1.18.33)packages/opencode/src/tool/websearch.ts(at v1.18.33)packages/opencode/src/tool/lsp.ts(at v1.18.33)packages/opencode/src/session/processor.ts(at v1.18.33)packages/opencode/src/tool/skill.ts(at v1.18.33)packages/opencode/src/session/tools.ts: MCP tool permission keys (at v1.18.33)packages/opencode/src/permission/arity.ts: bash command arity table (at v1.18.33)packages/opencode/src/cli/cmd/tui.ts: TUI command flags (--auto and aliases) (at v1.18.33)packages/opencode/src/cli/cmd/run.ts: opencode run flags and permission replies (at v1.18.33)packages/tui/src/context/sync.tsx: TUI auto mode replies to permission.asked (at v1.18.33)packages/tui/src/app.tsx: command palette auto-approve toggle (at v1.18.33)packages/tui/src/routes/session/permission.tsx: TUI permission prompt (at v1.18.33)packages/opencode/src/permission/index.ts: rule evaluation (last match wins), ask/reply, pattern home expansion (at v1.18.33)packages/core/src/util/wildcard.ts: wildcard matching (at v1.18.33)packages/opencode/src/agent/subagent-permissions.ts: subagent session permissions (at v1.18.33)packages/opencode/src/config/config.ts: OPENCODE_PERMISSION and legacy tools → permission (at v1.18.33)packages/opencode/src/config/v2-compat.ts: rejection of v2 `permissions` in 1.x config (at v1.18.33)
Release-by-release changes: OpenCode version tracker. All OpenCode pages: OpenCode reference index.