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 _ |
opencode_list_mcp_resources | MCP server name |
opencode_read_mcp_resource | <server>:<uri> |
execute | *; a deny removes Code Mode and the tools reached only through it, 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 is
checked against external_directory before its read or edit rules. The
default policy allows external_directory, so external paths are not prompted
unless you add a rule. To be asked before any access outside the project:
{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "external_directory", "resource": "*", "effect": "ask" },
],
}
Narrower rules after it can allow or deny specific directories:
{
"$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": "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 shell, reads, globs, grep, web fetches, web searches, and external directories; asks for .env reads |
title | Denies all actions |
summary | Denies all actions |
compaction | Keeps the base policy |
Because no default rule asks for external_directory, reads and edits outside
the Location and worktree proceed without a prompt. Add an
external_directory ask or deny rule, as shown in
Directories, to restore a prompt or block outside access.
Later global and agent rules 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.
{
"policies": [{ "action": "tool.use", "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.