---
title: "Permissions"
description: ""
---

Permissions control whether an agent may perform an action on a resource. V2
configuration uses the `permissions` field and an ordered array of rules.

<Callout type="warning">
  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`.
</Callout>

## Rule schema

Each rule has three required string fields:

```jsonc
{
  "$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 |

`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.

```jsonc
{
  "$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.

<Callout type="warning">
  `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.
</Callout>

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

Every agent, including custom agents, starts with ordered defaults that allow
tools, ask for external directories, ask for `.env` reads, and allow
`.env.example` reads. Shipped agents then add their own policies:

| 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:

```jsonc
[
  { "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, shell-output, temporary, and
global configuration directories. These exceptions apply only to the
external-directory boundary for every agent; the underlying action still uses
its own permission rules. The environment instructions identify the temporary
directory available for work outside the workspace. Later global and
agent-specific rules can override these defaults.

## Agent overrides

Configure shared policy at the top level and append narrower rules to a named
agent under `agents.<id>.permissions`:

```jsonc
{
  "$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.
