Skip to content

Commit 0efc616

Browse files
fix(opencode): agent create generates permissions field with deny ins… (#24482)
Co-authored-by: Aiden Cline <[email protected]> Co-authored-by: Aiden Cline <[email protected]>
1 parent 1e191ba commit 0efc616

3 files changed

Lines changed: 84 additions & 46 deletions

File tree

packages/opencode/src/cli/cmd/agent.ts

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import type { Argv } from "yargs"
1515

1616
type AgentMode = "all" | "primary" | "subagent"
1717

18-
const AVAILABLE_TOOLS = ["bash", "read", "write", "edit", "glob", "grep", "webfetch", "task", "todowrite"]
18+
// Permission keys (not raw tool names). Multiple tools can map to a single
19+
// permission — e.g. write/edit/apply_patch all gate on `edit` — so we configure
20+
// agents at the permission level to match how the runtime actually enforces it.
21+
const AVAILABLE_PERMISSIONS = ["bash", "read", "edit", "glob", "grep", "webfetch", "task", "todowrite", "websearch", "codesearch", "lsp", "skill"]
1922

2023
const AgentCreateCommand = cmd({
2124
command: "create",
@@ -35,9 +38,10 @@ const AgentCreateCommand = cmd({
3538
describe: "agent mode",
3639
choices: ["all", "primary", "subagent"] as const,
3740
})
38-
.option("tools", {
41+
.option("permissions", {
3942
type: "string",
40-
describe: `comma-separated list of tools to enable (default: all). Available: "${AVAILABLE_TOOLS.join(", ")}"`,
43+
alias: ["tools"],
44+
describe: `comma-separated list of permissions to allow (default: all). Available: "${AVAILABLE_PERMISSIONS.join(", ")}"`,
4145
})
4246
.option("model", {
4347
type: "string",
@@ -51,9 +55,9 @@ const AgentCreateCommand = cmd({
5155
const cliPath = args.path
5256
const cliDescription = args.description
5357
const cliMode = args.mode as AgentMode | undefined
54-
const cliTools = args.tools
58+
const perms = args.permissions
5559

56-
const isFullyNonInteractive = cliPath && cliDescription && cliMode && cliTools !== undefined
60+
const isFullyNonInteractive = cliPath && cliDescription && cliMode && perms !== undefined
5761

5862
if (!isFullyNonInteractive) {
5963
UI.empty()
@@ -120,21 +124,21 @@ const AgentCreateCommand = cmd({
120124
})
121125
spinner.stop(`Agent ${generated.identifier} generated`)
122126

123-
// Select tools
124-
let selectedTools: string[]
125-
if (cliTools !== undefined) {
126-
selectedTools = cliTools ? cliTools.split(",").map((t) => t.trim()) : AVAILABLE_TOOLS
127+
// Select permissions to allow
128+
let selected: string[]
129+
if (perms !== undefined) {
130+
selected = perms ? perms.split(",").map((t) => t.trim()) : AVAILABLE_PERMISSIONS
127131
} else {
128132
const result = await prompts.multiselect({
129-
message: "Select tools to enable (Space to toggle)",
130-
options: AVAILABLE_TOOLS.map((tool) => ({
131-
label: tool,
132-
value: tool,
133+
message: "Select permissions to allow (Space to toggle)",
134+
options: AVAILABLE_PERMISSIONS.map((permission) => ({
135+
label: permission,
136+
value: permission,
133137
})),
134-
initialValues: AVAILABLE_TOOLS,
138+
initialValues: AVAILABLE_PERMISSIONS,
135139
})
136140
if (prompts.isCancel(result)) throw new UI.CancelledError()
137-
selectedTools = result
141+
selected = result
138142
}
139143

140144
// Get mode
@@ -167,25 +171,25 @@ const AgentCreateCommand = cmd({
167171
mode = modeResult
168172
}
169173

170-
// Build tools config
171-
const tools: Record<string, boolean> = {}
172-
for (const tool of AVAILABLE_TOOLS) {
173-
if (!selectedTools.includes(tool)) {
174-
tools[tool] = false
174+
// Build permissions config — deny anything not explicitly selected.
175+
const permissions: Record<string, "deny"> = {}
176+
for (const permission of AVAILABLE_PERMISSIONS) {
177+
if (!selected.includes(permission)) {
178+
permissions[permission] = "deny"
175179
}
176180
}
177181

178182
// Build frontmatter
179183
const frontmatter: {
180184
description: string
181185
mode: AgentMode
182-
tools?: Record<string, boolean>
186+
permission?: Record<string, "deny">
183187
} = {
184188
description: generated.whenToUse,
185189
mode,
186190
}
187-
if (Object.keys(tools).length > 0) {
188-
frontmatter.tools = tools
191+
if (Object.keys(permissions).length > 0) {
192+
frontmatter.permission = permissions
189193
}
190194

191195
// Write file

packages/web/src/content/docs/agents.mdx

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -149,29 +149,26 @@ Configure agents in your `opencode.json` config file:
149149
"mode": "primary",
150150
"model": "anthropic/claude-sonnet-4-20250514",
151151
"prompt": "{file:./prompts/build.txt}",
152-
"tools": {
153-
"write": true,
154-
"edit": true,
155-
"bash": true
152+
"permission": {
153+
"edit": "allow",
154+
"bash": "allow"
156155
}
157156
},
158157
"plan": {
159158
"mode": "primary",
160159
"model": "anthropic/claude-haiku-4-20250514",
161-
"tools": {
162-
"write": false,
163-
"edit": false,
164-
"bash": false
160+
"permission": {
161+
"edit": "deny",
162+
"bash": "deny"
165163
}
166164
},
167165
"code-reviewer": {
168166
"description": "Reviews code for best practices and potential issues",
169167
"mode": "subagent",
170168
"model": "anthropic/claude-sonnet-4-20250514",
171169
"prompt": "You are a code reviewer. Focus on security, performance, and maintainability.",
172-
"tools": {
173-
"write": false,
174-
"edit": false
170+
"permission": {
171+
"edit": "deny"
175172
}
176173
}
177174
}
@@ -193,10 +190,9 @@ description: Reviews code for quality and best practices
193190
mode: subagent
194191
model: anthropic/claude-sonnet-4-20250514
195192
temperature: 0.1
196-
tools:
197-
write: false
198-
edit: false
199-
bash: false
193+
permission:
194+
edit: deny
195+
bash: deny
200196
---
201197

202198
You are in code review mode. Focus on:
@@ -417,12 +413,39 @@ You can also use wildcards in legacy `tools` entries to control multiple tools a
417413

418414
### Permissions
419415

420-
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:
416+
You can configure permissions to manage what actions an agent can take. Each permission key can be set to:
421417

422418
- `"ask"` — Prompt for approval before running the tool
423419
- `"allow"` — Allow all operations without approval
424420
- `"deny"` — Disable the tool
425421

422+
The available permission keys are:
423+
424+
| Key | Tools it gates |
425+
| -------------------- | ----------------------------------------------------------------------------- |
426+
| `read` | `read` |
427+
| `edit` | `write`, `edit`, `apply_patch` |
428+
| `glob` | `glob` |
429+
| `grep` | `grep` |
430+
| `list` | `list` |
431+
| `bash` | `bash` |
432+
| `task` | `task` |
433+
| `external_directory` | Any tool that reads or writes files outside the project worktree |
434+
| `todowrite` | `todowrite`, `todoread` |
435+
| `webfetch` | `webfetch` |
436+
| `websearch` | `websearch` |
437+
| `codesearch` | `codesearch` |
438+
| `lsp` | `lsp` |
439+
| `skill` | `skill` |
440+
| `question` | `question` |
441+
| `doom_loop` | Recovery prompts when an agent appears stuck |
442+
443+
`read`, `edit`, `glob`, `grep`, `list`, `bash`, `task`, `external_directory`, `lsp`, and `skill` accept either a shorthand action (`"allow" | "ask" | "deny"`) or an object of glob/pattern → action for fine-grained control. The remaining keys accept the shorthand action only.
444+
445+
:::note
446+
Permission keys are matched as wildcard patterns against the underlying tool name, so the same syntax works for built-ins, custom tools, and MCP tools — for example `"mymcp_*": "deny"` denies every tool from an MCP server, and `"mymcp_search": "ask"` targets a single one.
447+
:::
448+
426449
```json title="opencode.json"
427450
{
428451
"$schema": "https://opencode.ai/config.json",
@@ -680,7 +703,7 @@ This interactive command will:
680703
1. Ask where to save the agent; global or project-specific.
681704
2. Description of what the agent should do.
682705
3. Generate an appropriate system prompt and identifier.
683-
4. Let you select which tools the agent can access.
706+
4. Let you select which permissions the agent should be allowed (anything you don't select is denied).
684707
5. Finally, create a markdown file with the agent configuration.
685708

686709
---
@@ -713,8 +736,8 @@ Do you have an agent you'd like to share? [Submit a PR](https://github.com/anoma
713736
---
714737
description: Writes and maintains project documentation
715738
mode: subagent
716-
tools:
717-
bash: false
739+
permission:
740+
bash: deny
718741
---
719742

720743
You are a technical writer. Create clear, comprehensive documentation.
@@ -735,9 +758,8 @@ Focus on:
735758
---
736759
description: Performs security audits and identifies vulnerabilities
737760
mode: subagent
738-
tools:
739-
write: false
740-
edit: false
761+
permission:
762+
edit: deny
741763
---
742764

743765
You are a security expert. Focus on identifying potential security issues.

packages/web/src/content/docs/cli.mdx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,19 @@ Create a new agent with custom configuration.
9393
opencode agent create
9494
```
9595

96-
This command will guide you through creating a new agent with a custom system prompt and tool configuration.
96+
This command will guide you through creating a new agent with a custom system prompt and permission configuration. Anything you don't allow is denied in the generated agent's frontmatter.
97+
98+
#### Flags
99+
100+
| Flag | Description |
101+
| ---------------- | ---------------------------------------------------------------------------------------------------------- |
102+
| `--path` | Directory to write the agent file to (defaults to global or `.opencode/agent` based on the prompt) |
103+
| `--description` | What the agent should do |
104+
| `--mode` | Agent mode: `all`, `primary`, or `subagent` |
105+
| `--permissions` | Comma-separated list of permissions to allow (default: all). Available: `bash`, `read`, `edit`, `glob`, `grep`, `webfetch`, `task`, `todowrite`, `websearch`, `codesearch`, `lsp`, `skill`. Anything omitted is denied. Alias: `--tools` |
106+
| `--model`, `-m` | Model to use, in `provider/model` format |
107+
108+
Passing all of `--path`, `--description`, `--mode`, and `--permissions` runs the command non-interactively.
97109

98110
---
99111

0 commit comments

Comments
 (0)