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

## Experimental shell scanner

Set `experimental.portable_shell_scanner` to `true` to test the portable shell
permission scanner. The default remains the tree-sitter scanner.

```jsonc
{
  "$schema": "https://opencode.ai/config.json",
  "experimental": {
    "portable_shell_scanner": true,
  },
}
```

When enabled, the portable scanner is authoritative and tree-sitter is not
consulted. Unsupported syntax, including heredocs, requests permission for the
original shell command without inferring external directories.

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