Permissions
Permissions control whether an agent may perform an action on a resource.
Configure
A common setup is to ask before running shell commands, allow routine Git
inspection, and always block pushes. Add these ordered rules to opencode.jsonc:
{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "shell", "resource": "*", "effect": "ask" },
{ "action": "shell", "resource": "git status *", "effect": "allow" },
{ "action": "shell", "resource": "git diff *", "effect": "allow" },
{ "action": "shell", "resource": "git push *", "effect": "deny" },
],
}
The last matching rule wins, so the specific exceptions follow the broad rule.
Rules
Each rule requires three string fields:
| Field | Meaning |
|---|---|
action | Tool permission action |
resource | Value being used, such as a path, command, URL, query, skill ID, or agent ID |
effect | allow, deny, or ask |
{
"permissions": [
{ "action": "read", "resource": "*", "effect": "allow" },
{ "action": "read", "resource": "*.env", "effect": "deny" },
],
}
| Effect | Result |
|---|---|
allow | Continue without prompting |
deny | Block the operation |
ask | Wait for a decision from the client |
If no rule matches, OpenCode uses ask.
Matching
Actions and resources use simple whole-value wildcards:
| Pattern | Match |
|---|---|
* | Zero or more characters, including / |
? | Exactly one character |
| Other | The literal character |
{
"permissions": [
{ "action": "edit", "resource": "packages/docs/*.mdx", "effect": "allow" },
],
}
This pattern matches the entire normalized path. Backslashes are normalized to slashes, and matching is case-insensitive on Windows.
A shell pattern ending in * also matches the command without arguments:
{ "action": "shell", "resource": "git status *", "effect": "allow" }
This matches both git status and git status --short.
OpenCode combines rules in order and uses the last match. Lower-priority configuration is loaded first, global rules are appended next, and agent rules are appended last.
{
"permissions": [
{ "action": "read", "resource": "*", "effect": "allow" },
{ "action": "read", "resource": "secrets/*", "effect": "deny" },
],
}
Operations may check several resources, such as a patch that touches multiple
files. Any deny denies the operation; otherwise any ask asks; otherwise the
operation is allowed.
Actions
V2 action names are strings, so plugins may define more actions. Built-in tools currently use these actions and resources:
| Action | Resource |
|---|---|
read | Location-relative internal path or canonical absolute external path |
edit | Target path for edit, write, and patch |
glob | Requested glob pattern |
grep | Requested regular expression, not the search path |
shell | Scanner-produced command string; compound commands may produce several |
subagent | Target agent ID |
skill | Skill ID |
question | * |
webfetch | Requested URL |
websearch | Search query |
external_directory | Canonical external directory boundary, normally ending in /* |
<server>_<tool> | * for an MCP tool; unsupported characters in both names become _ |
execute | *; controls Code Mode availability, while nested tools enforce their rules |
For example, allow one skill and deny all other skills:
{
"permissions": [
{ "action": "skill", "resource": "*", "effect": "deny" },
{ "action": "skill", "resource": "effect", "effect": "allow" },
],
}
doom_loop and lsp are not current V2 Core permission actions.
Directories
A path outside both the active Location and its non-root project worktree needs
external_directory approval before its read or edit approval.
{
"$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" },
],
}
This applies to external paths used by read, edit, write, and patch.
Shell checks its external working directory and directories inferred by its
scanner before checking shell resources.
For external_directory, read, and edit, a leading ~, ~/, $HOME, or
$HOME/ is expanded when configuration loads:
{ "action": "read", "resource": "$HOME/reference/*", "effect": "allow" }
Shell resources remain raw command text and are not home-expanded.
Relative mutation paths may leave the active Location while remaining inside its project worktree. Explicit external paths are canonicalized before matching, so authorize only trusted directory boundaries.
Scanner
Enable the experimental portable shell scanner with:
{
"$schema": "https://opencode.ai/config.json",
"experimental": {
"portable_shell_scanner": true,
},
}
The portable scanner replaces the default tree-sitter scanner; tree-sitter is not a fallback or second opinion. A command the portable scanner cannot analyze returns a scanner error, not a permission denial.
The flag changes only parser selection. Existing permission rules, saved approval patterns, and best-effort directory inference still apply. There is no extra approval mode or blanket restriction for unknown directories.
Defaults
Every agent, including custom agents, starts with this ordered base policy:
[
{ "action": "*", "resource": "*", "effect": "allow" },
{ "action": "external_directory", "resource": "*", "effect": "ask" },
{ "action": "read", "resource": "*.env", "effect": "ask" },
{ "action": "read", "resource": "*.env.*", "effect": "ask" },
{ "action": "read", "resource": "*.env.example", "effect": "allow" },
]
Shipped agents append these policies:
| Agent | Additional policy |
|---|---|
build | Allows questions |
plan | Allows questions; denies edits except files under ~/.opencode/plan |
general | Denies questions and launching subagents |
explore | Denies everything except reads, globs, grep, web fetches, and web searches; asks for external directories and .env reads |
title | Denies all actions |
summary | Denies all actions |
compaction | Keeps the base policy |
OpenCode also allows external-directory access to its managed tool-output,
shell-output, temporary, and global configuration directories. The underlying
read, edit, or other action still uses its own rules. Later global and agent
rules can override these defaults.
Agents
Put shared rules at the top level and narrower rules under
agents.<id>.permissions:
{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "shell", "resource": "*", "effect": "ask" },
{ "action": "shell", "resource": "git status *", "effect": "allow" },
],
"agents": {
"reviewer": {
"description": "Review code without changing it",
"mode": "subagent",
"permissions": [
{ "action": "edit", "resource": "*", "effect": "deny" },
],
},
},
}
Agent rules are appended after global rules; they do not replace the global array. A custom subagent uses its own permissions, not a subset of its parent’s permissions.
Approvals
When a rule resolves to ask, clients can reply with:
| Choice | Reply | Result |
|---|---|---|
| Allow once | once | Approve only the pending request |
| Allow always | always | Approve it and save the tool’s proposed patterns for the project |
| Reject | reject | Reject it and every other pending permission request in that session |
For example, choosing Allow always for git status --short may save a
shell prefix that covers later git status commands:
shell: git status * → allow
Saved approvals are durable, project-scoped allow rules. They never override
a configured deny. Tools choose the proposed saved pattern: some propose *,
shell proposes command prefixes, and skills and subagents propose their IDs.
Review broad approvals and remove those no longer needed.
Clients may attach feedback when rejecting. Non-interactive clients must decide
how to handle approval requests; configured deny rules always remain enforced.
Policies
A policy can hard-deny a permission check after these rules and saved
approvals run. It turns allow or ask into deny and never grants access.
{
"experimental": {
"policies": [{ "action": "permission", "resource": "shell:sudo *", "effect": "deny" }],
},
}
Global and Console-managed policies override project configuration, which is how an organization blocks a command that a repository would allow.