Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/web/src/content/docs/agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ You can also use wildcards to control multiple tools at once. For example, to di

### Permissions

You can configure permissions to manage what actions an agent can take. Currently, the permissions for the `edit`, `bash`, and `webfetch` tools can be configured to:
You can configure permissions to manage what actions an agent can take. Most built-in tools support permissions (for example, `edit`, `bash`, `read`, `webfetch`, `glob`, `grep`, `task`, etc.), plus special gates like `external_directory` and `doom_loop`.

- `"ask"` — Prompt for approval before running the tool
- `"allow"` — Allow all operations without approval
Expand Down
59 changes: 52 additions & 7 deletions packages/web/src/content/docs/permissions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ By default, OpenCode allows most operations without approval, except `doom_loop`
"permission": {
"edit": "allow",
"bash": "ask",
"read": "allow",
"skill": "ask",
"webfetch": "deny",
"doom_loop": "ask",
Expand All @@ -19,7 +20,7 @@ By default, OpenCode allows most operations without approval, except `doom_loop`
}
```

This lets you configure granular controls for the `edit`, `bash`, `skill`, `webfetch`, `doom_loop`, and `external_directory` tools.
This lets you configure granular controls for most built-in tools (and many plugin tools). Note that `write`, `patch`, and `multiedit` are all covered by `permission.edit`.

- `"ask"` — Prompt for approval before running the tool
- `"allow"` — Allow all operations without approval
Expand All @@ -29,7 +30,26 @@ This lets you configure granular controls for the `edit`, `bash`, `skill`, `webf

## Tools

Currently, the permissions for the `edit`, `bash`, `skill`, `webfetch`, `doom_loop`, and `external_directory` tools can be configured through the `permission` option.
Any tool that might access your filesystem, run commands, or use the network can be gated behind permissions.

Common permission keys:

- `edit` (also covers `write`, `patch`, `multiedit`)
- `bash`
- `read`
- `list`
- `glob`
- `grep`
- `lsp`
- `task`
- `todowrite` / `todoread`
- `skill`
- `webfetch`
- `websearch` / `codesearch`
- `doom_loop`
- `external_directory`

Each permission key can be either a single action (string), or an object mapping patterns to actions.

---

Expand Down Expand Up @@ -135,13 +155,38 @@ The wildcard uses simple regex globbing patterns.

#### Scope of the `"ask"` option

When the agent asks for permission to run a particular bash command, it will
request feedback with the three options "accept", "accept always" and "deny".
The "accept always" answer applies for the rest of the current session.
When the agent asks for permission to run a particular bash command, it will request feedback with three options: `once`, `always`, and `reject`.

The `always` option stores a rule for a normalized **command prefix** (for example, `git status*` or `npm run dev*`). The prefix is derived from a built-in command-arity table (flags don’t count); for unknown commands it falls back to the first token.

When an agent asks for permission to run a command in a pipeline, each command is parsed separately, and the saved `always` rule applies per command.

---

### read

Use the `permission.read` key to control whether file reads require approval.

The permission patterns for `read` are absolute file paths.

```json title="opencode.json" {4}
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"read": "ask"
}
}
```

---

### list, glob, grep

In addition, command permissions are applied to the first two elements of a command. So, an "accept always" response for a command like `git log` would whitelist `git log *` but not `git commit ...`.
You can also gate other "read-only" tools:

When an agent asks for permission to run a command in a pipeline, we use tree sitter to parse each command in the pipeline. The "accept always" permission thus applies separately to each command in the pipeline.
- `permission.list` — directory listings (patterns are directory paths)
- `permission.glob` — file globs (patterns are the glob patterns)
- `permission.grep` — content search (patterns are the regex patterns)

---

Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/content/docs/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ Plugins can subscribe to events as seen below in the Examples section. Here is a

#### Permission Events

- `permission.asked`
- `permission.replied`
- `permission.updated`

#### Server Events

Expand Down
51 changes: 30 additions & 21 deletions packages/web/src/content/docs/sdk.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -226,27 +226,36 @@ const { providers, default: defaults } = await client.config.providers()

### Sessions

| Method | Description | Notes |
| ---------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `session.list()` | List sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
| `session.get({ path })` | Get session | Returns <a href={typesUrl}><code>Session</code></a> |
| `session.children({ path })` | List child sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
| `session.create({ body })` | Create session | Returns <a href={typesUrl}><code>Session</code></a> |
| `session.delete({ path })` | Delete session | Returns `boolean` |
| `session.update({ path, body })` | Update session properties | Returns <a href={typesUrl}><code>Session</code></a> |
| `session.init({ path, body })` | Analyze app and create `AGENTS.md` | Returns `boolean` |
| `session.abort({ path })` | Abort a running session | Returns `boolean` |
| `session.share({ path })` | Share session | Returns <a href={typesUrl}><code>Session</code></a> |
| `session.unshare({ path })` | Unshare session | Returns <a href={typesUrl}><code>Session</code></a> |
| `session.summarize({ path, body })` | Summarize session | Returns `boolean` |
| `session.messages({ path })` | List messages in a session | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}[]` |
| `session.message({ path })` | Get message details | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}` |
| `session.prompt({ path, body })` | Send prompt message | `body.noReply: true` returns UserMessage (context only). Default returns <a href={typesUrl}><code>AssistantMessage</code></a> with AI response |
| `session.command({ path, body })` | Send command to session | Returns `{ info: `<a href={typesUrl}><code>AssistantMessage</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}` |
| `session.shell({ path, body })` | Run a shell command | Returns <a href={typesUrl}><code>AssistantMessage</code></a> |
| `session.revert({ path, body })` | Revert a message | Returns <a href={typesUrl}><code>Session</code></a> |
| `session.unrevert({ path })` | Restore reverted messages | Returns <a href={typesUrl}><code>Session</code></a> |
| `postSessionByIdPermissionsByPermissionId({ path, body })` | Respond to a permission request | Returns `boolean` |
| Method | Description | Notes |
| ----------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `session.list()` | List sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
| `session.get({ path })` | Get session | Returns <a href={typesUrl}><code>Session</code></a> |
| `session.children({ path })` | List child sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
| `session.create({ body })` | Create session | Returns <a href={typesUrl}><code>Session</code></a> |
| `session.delete({ path })` | Delete session | Returns `boolean` |
| `session.update({ path, body })` | Update session properties | Returns <a href={typesUrl}><code>Session</code></a> |
| `session.init({ path, body })` | Analyze app and create `AGENTS.md` | Returns `boolean` |
| `session.abort({ path })` | Abort a running session | Returns `boolean` |
| `session.share({ path })` | Share session | Returns <a href={typesUrl}><code>Session</code></a> |
| `session.unshare({ path })` | Unshare session | Returns <a href={typesUrl}><code>Session</code></a> |
| `session.summarize({ path, body })` | Summarize session | Returns `boolean` |
| `session.messages({ path })` | List messages in a session | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}[]` |
| `session.message({ path })` | Get message details | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}` |
| `session.prompt({ path, body })` | Send prompt message | `body.noReply: true` returns UserMessage (context only). Default returns <a href={typesUrl}><code>AssistantMessage</code></a> with AI response |
| `session.command({ path, body })` | Send command to session | Returns `{ info: `<a href={typesUrl}><code>AssistantMessage</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}` |
| `session.shell({ path, body })` | Run a shell command | Returns <a href={typesUrl}><code>AssistantMessage</code></a> |
| `session.revert({ path, body })` | Revert a message | Returns <a href={typesUrl}><code>Session</code></a> |
| `session.unrevert({ path })` | Restore reverted messages | Returns <a href={typesUrl}><code>Session</code></a> |

---

### Permissions

| Method | Description | Notes |
| ----------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------ |
| `permission.list()` | List pending permissions | Returns <a href={typesUrl}><code>PermissionRequest[]</code></a> |
| `permission.reply({ requestID, reply })` | Respond to a permission request | `reply` is `"once"`, `"always"`, or `"reject"` (returns `boolean`) |
| `permission.respond({ sessionID, permissionID, response })` | Respond to a permission request | Deprecated alias (returns `boolean`) |

---

Expand Down
Loading