Permissions control whether an agent may perform an action on a resource. V2
configuration uses the permissions field and an ordered array of rules.
The V1 object syntax uses different field and action names. Do not use
permission, bash, or task in V2 configuration; use permissions,
shell, and subagent.
Rule schema
Each rule has three required string fields:
{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "*", "resource": "*", "effect": "ask" },
{ "action": "read", "resource": "*", "effect": "allow" },
{ "action": "read", "resource": "*.env", "effect": "deny" },
{ "action": "shell", "resource": "git status *", "effect": "allow" },
{ "action": "shell", "resource": "git push *", "effect": "deny" },
{ "action": "edit", "resource": "packages/docs/*.mdx", "effect": "allow" }
]
}
action matches a tool permission action.
resource matches the value the tool is trying to use, such as a path,
command, URL, query, or agent ID.
effect is "allow", "deny", or "ask".
allow proceeds without prompting, deny blocks the operation, and ask
waits for a user decision. If no rule matches, the result is ask.
Matching and order
Both action and resource support simple wildcards:
* matches zero or more characters, including /.
? matches exactly one character.
- All other characters are literal.
Matches cover the entire value. Slashes are normalized, and matching is
case-insensitive on Windows. For shell convenience, a pattern ending in
" *" also matches the command without arguments: "git status *" matches
both git status and git status --short.
The last matching rule wins. Put broad rules first and exceptions later.
Rules from lower-priority configuration files are loaded first. OpenCode then
appends all global rules before agent-specific rules, so a matching agent rule
overrides a global rule.
Some operations check several resources at once, such as a patch touching
multiple files. OpenCode denies the operation if any resource resolves to
deny; otherwise it asks if any resolves to ask; otherwise it allows it.
Actions and resources
V2 action names are strings, so plugins may introduce additional actions. The
current built-in actions use these resources:
| Action | Resource matched |
|---|
read | Location-relative path for an internal file or directory; canonical absolute path for an external target |
edit | Target path for edit, write, and patch; all three tools share this action |
glob | The requested glob pattern |
grep | The requested regular expression, not the search path |
shell | The complete raw shell command string |
subagent | The target agent ID |
skill | The skill ID |
question | * |
webfetch | The requested URL |
websearch | The search query |
external_directory | A canonical external directory boundary, normally ending in /* |
<server>_<tool> | * for an MCP tool; unsupported characters in both names become _ |
execute | *; controls availability of the Code Mode dispatcher, while each nested tool still enforces its own permission |
Built-in agent policy also reserves plan_enter and plan_exit for plan-mode
transitions. doom_loop and lsp are not current V2 Core permission actions.
External directories
An external path requires a separate external_directory decision before the
tool’s own read or edit decision. This applies to external paths used by
read, edit, write, and patch, and to an external shell working
directory.
{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{
"action": "external_directory",
"resource": "~/projects/reference/*",
"effect": "allow"
},
{
"action": "read",
"resource": "~/projects/reference/*",
"effect": "allow"
},
{
"action": "edit",
"resource": "~/projects/reference/*",
"effect": "deny"
}
]
}
For external_directory, read, and edit resources, a leading ~, ~/,
$HOME, or $HOME/ is expanded when configuration loads. Shell resources are
raw command text and are not home-expanded.
shell runs with the host user’s filesystem, process, and network authority.
Its resource is raw text, not a parsed command. External command arguments
produce only best-effort warnings; external_directory is enforced for the
working directory, not every path embedded in a command. Prefer a narrow
shell allowlist over patterns intended to identify every dangerous command.
Relative mutation paths cannot escape the active Location, and symlink escapes
from inside it are rejected. Explicit external paths are canonicalized before
matching, so authorize only trusted directory boundaries.
Defaults
The evaluator’s fallback is ask, but shipped agents include ordered defaults:
| Agent | Effective default policy |
|---|
build | Allows most actions; asks for external directories and .env reads; allows questions and entering plan mode; denies exiting plan mode |
plan | Uses the same base, allows questions and exiting plan mode, and denies edits except OpenCode plan files |
general | Uses the base policy but cannot launch another subagent; questions and plan transitions remain denied |
explore | Denies everything except read, glob, grep, webfetch, and websearch; cannot launch subagents and asks for external directories |
| Hidden maintenance agents | Deny all actions |
The base read rules are ordered as follows:
[
{ "action": "read", "resource": "*", "effect": "allow" },
{ "action": "read", "resource": "*.env", "effect": "ask" },
{ "action": "read", "resource": "*.env.*", "effect": "ask" },
{ "action": "read", "resource": "*.env.example", "effect": "allow" }
]
OpenCode also permits its managed tool-output and temporary directories where
needed. These exceptions do not grant general external-directory access.
Agent overrides
Configure shared policy at the top level and append narrower rules to a named
agent under agents.<id>.permissions:
{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "shell", "resource": "*", "effect": "ask" },
{ "action": "shell", "resource": "git diff *", "effect": "allow" },
{ "action": "shell", "resource": "git status *", "effect": "allow" }
],
"agents": {
"reviewer": {
"description": "Review code without changing it",
"mode": "subagent",
"permissions": [
{ "action": "edit", "resource": "*", "effect": "deny" },
{ "action": "shell", "resource": "git diff *", "effect": "allow" },
{ "action": "shell", "resource": "git status *", "effect": "allow" }
]
}
}
}
Agent rules do not replace the global array; they are appended after it. A
custom subagent executes with its own permissions, not a permission subset
derived from the parent agent.
Approval choices
When an ask rule matches, clients can reply with:
- Allow once (
once): approve only the pending request.
- Allow always (
always): approve this request and save the patterns
proposed by the tool for the current project.
- Reject (
reject): reject the request. Rejecting also rejects other
pending permission requests in the same session; clients may attach feedback.
Saved approvals are durable and project-scoped. They are additional allow
rules, but they can never override a configured deny. The proposed saved
pattern may be broader than the displayed resource: several tools propose *,
shell proposes the exact command text, and skills and subagents propose their
IDs. Review the confirmation carefully and remove saved approvals that are no
longer needed.
For non-interactive runs, opencode2 run --auto replies once to permission
requests. It does not save approvals, and explicit deny rules remain enforced.
Without --auto, a non-interactive run rejects permission requests.