fix(opencode): agent create generates permissions field with deny ins… (#24482)

Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
This commit is contained in:
21pounder
2026-04-27 00:17:08 -05:00
committed by GitHub
co-authored by Aiden Cline Aiden Cline
parent 1e191ba815
commit 0efc6163f1
3 changed files with 84 additions and 46 deletions
+27 -23
View File
@@ -15,7 +15,10 @@ import type { Argv } from "yargs"
type AgentMode = "all" | "primary" | "subagent" type AgentMode = "all" | "primary" | "subagent"
const AVAILABLE_TOOLS = ["bash", "read", "write", "edit", "glob", "grep", "webfetch", "task", "todowrite"] // Permission keys (not raw tool names). Multiple tools can map to a single
// permission — e.g. write/edit/apply_patch all gate on `edit` — so we configure
// agents at the permission level to match how the runtime actually enforces it.
const AVAILABLE_PERMISSIONS = ["bash", "read", "edit", "glob", "grep", "webfetch", "task", "todowrite", "websearch", "codesearch", "lsp", "skill"]
const AgentCreateCommand = cmd({ const AgentCreateCommand = cmd({
command: "create", command: "create",
@@ -35,9 +38,10 @@ const AgentCreateCommand = cmd({
describe: "agent mode", describe: "agent mode",
choices: ["all", "primary", "subagent"] as const, choices: ["all", "primary", "subagent"] as const,
}) })
.option("tools", { .option("permissions", {
type: "string", type: "string",
describe: `comma-separated list of tools to enable (default: all). Available: "${AVAILABLE_TOOLS.join(", ")}"`, alias: ["tools"],
describe: `comma-separated list of permissions to allow (default: all). Available: "${AVAILABLE_PERMISSIONS.join(", ")}"`,
}) })
.option("model", { .option("model", {
type: "string", type: "string",
@@ -51,9 +55,9 @@ const AgentCreateCommand = cmd({
const cliPath = args.path const cliPath = args.path
const cliDescription = args.description const cliDescription = args.description
const cliMode = args.mode as AgentMode | undefined const cliMode = args.mode as AgentMode | undefined
const cliTools = args.tools const perms = args.permissions
const isFullyNonInteractive = cliPath && cliDescription && cliMode && cliTools !== undefined const isFullyNonInteractive = cliPath && cliDescription && cliMode && perms !== undefined
if (!isFullyNonInteractive) { if (!isFullyNonInteractive) {
UI.empty() UI.empty()
@@ -120,21 +124,21 @@ const AgentCreateCommand = cmd({
}) })
spinner.stop(`Agent ${generated.identifier} generated`) spinner.stop(`Agent ${generated.identifier} generated`)
// Select tools // Select permissions to allow
let selectedTools: string[] let selected: string[]
if (cliTools !== undefined) { if (perms !== undefined) {
selectedTools = cliTools ? cliTools.split(",").map((t) => t.trim()) : AVAILABLE_TOOLS selected = perms ? perms.split(",").map((t) => t.trim()) : AVAILABLE_PERMISSIONS
} else { } else {
const result = await prompts.multiselect({ const result = await prompts.multiselect({
message: "Select tools to enable (Space to toggle)", message: "Select permissions to allow (Space to toggle)",
options: AVAILABLE_TOOLS.map((tool) => ({ options: AVAILABLE_PERMISSIONS.map((permission) => ({
label: tool, label: permission,
value: tool, value: permission,
})), })),
initialValues: AVAILABLE_TOOLS, initialValues: AVAILABLE_PERMISSIONS,
}) })
if (prompts.isCancel(result)) throw new UI.CancelledError() if (prompts.isCancel(result)) throw new UI.CancelledError()
selectedTools = result selected = result
} }
// Get mode // Get mode
@@ -167,11 +171,11 @@ const AgentCreateCommand = cmd({
mode = modeResult mode = modeResult
} }
// Build tools config // Build permissions config — deny anything not explicitly selected.
const tools: Record<string, boolean> = {} const permissions: Record<string, "deny"> = {}
for (const tool of AVAILABLE_TOOLS) { for (const permission of AVAILABLE_PERMISSIONS) {
if (!selectedTools.includes(tool)) { if (!selected.includes(permission)) {
tools[tool] = false permissions[permission] = "deny"
} }
} }
@@ -179,13 +183,13 @@ const AgentCreateCommand = cmd({
const frontmatter: { const frontmatter: {
description: string description: string
mode: AgentMode mode: AgentMode
tools?: Record<string, boolean> permission?: Record<string, "deny">
} = { } = {
description: generated.whenToUse, description: generated.whenToUse,
mode, mode,
} }
if (Object.keys(tools).length > 0) { if (Object.keys(permissions).length > 0) {
frontmatter.tools = tools frontmatter.permission = permissions
} }
// Write file // Write file
+44 -22
View File
@@ -149,19 +149,17 @@ Configure agents in your `opencode.json` config file:
"mode": "primary", "mode": "primary",
"model": "anthropic/claude-sonnet-4-20250514", "model": "anthropic/claude-sonnet-4-20250514",
"prompt": "{file:./prompts/build.txt}", "prompt": "{file:./prompts/build.txt}",
"tools": { "permission": {
"write": true, "edit": "allow",
"edit": true, "bash": "allow"
"bash": true
} }
}, },
"plan": { "plan": {
"mode": "primary", "mode": "primary",
"model": "anthropic/claude-haiku-4-20250514", "model": "anthropic/claude-haiku-4-20250514",
"tools": { "permission": {
"write": false, "edit": "deny",
"edit": false, "bash": "deny"
"bash": false
} }
}, },
"code-reviewer": { "code-reviewer": {
@@ -169,9 +167,8 @@ Configure agents in your `opencode.json` config file:
"mode": "subagent", "mode": "subagent",
"model": "anthropic/claude-sonnet-4-20250514", "model": "anthropic/claude-sonnet-4-20250514",
"prompt": "You are a code reviewer. Focus on security, performance, and maintainability.", "prompt": "You are a code reviewer. Focus on security, performance, and maintainability.",
"tools": { "permission": {
"write": false, "edit": "deny"
"edit": false
} }
} }
} }
@@ -193,10 +190,9 @@ description: Reviews code for quality and best practices
mode: subagent mode: subagent
model: anthropic/claude-sonnet-4-20250514 model: anthropic/claude-sonnet-4-20250514
temperature: 0.1 temperature: 0.1
tools: permission:
write: false edit: deny
edit: false bash: deny
bash: false
--- ---
You are in code review mode. Focus on: 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
### Permissions ### 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. Each permission key can be set to:
- `"ask"` — Prompt for approval before running the tool - `"ask"` — Prompt for approval before running the tool
- `"allow"` — Allow all operations without approval - `"allow"` — Allow all operations without approval
- `"deny"` — Disable the tool - `"deny"` — Disable the tool
The available permission keys are:
| Key | Tools it gates |
| -------------------- | ----------------------------------------------------------------------------- |
| `read` | `read` |
| `edit` | `write`, `edit`, `apply_patch` |
| `glob` | `glob` |
| `grep` | `grep` |
| `list` | `list` |
| `bash` | `bash` |
| `task` | `task` |
| `external_directory` | Any tool that reads or writes files outside the project worktree |
| `todowrite` | `todowrite`, `todoread` |
| `webfetch` | `webfetch` |
| `websearch` | `websearch` |
| `codesearch` | `codesearch` |
| `lsp` | `lsp` |
| `skill` | `skill` |
| `question` | `question` |
| `doom_loop` | Recovery prompts when an agent appears stuck |
`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.
:::note
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.
:::
```json title="opencode.json" ```json title="opencode.json"
{ {
"$schema": "https://opencode.ai/config.json", "$schema": "https://opencode.ai/config.json",
@@ -680,7 +703,7 @@ This interactive command will:
1. Ask where to save the agent; global or project-specific. 1. Ask where to save the agent; global or project-specific.
2. Description of what the agent should do. 2. Description of what the agent should do.
3. Generate an appropriate system prompt and identifier. 3. Generate an appropriate system prompt and identifier.
4. Let you select which tools the agent can access. 4. Let you select which permissions the agent should be allowed (anything you don't select is denied).
5. Finally, create a markdown file with the agent configuration. 5. Finally, create a markdown file with the agent configuration.
--- ---
@@ -713,8 +736,8 @@ Do you have an agent you'd like to share? [Submit a PR](https://github.com/anoma
--- ---
description: Writes and maintains project documentation description: Writes and maintains project documentation
mode: subagent mode: subagent
tools: permission:
bash: false bash: deny
--- ---
You are a technical writer. Create clear, comprehensive documentation. You are a technical writer. Create clear, comprehensive documentation.
@@ -735,9 +758,8 @@ Focus on:
--- ---
description: Performs security audits and identifies vulnerabilities description: Performs security audits and identifies vulnerabilities
mode: subagent mode: subagent
tools: permission:
write: false edit: deny
edit: false
--- ---
You are a security expert. Focus on identifying potential security issues. You are a security expert. Focus on identifying potential security issues.
+13 -1
View File
@@ -93,7 +93,19 @@ Create a new agent with custom configuration.
opencode agent create opencode agent create
``` ```
This command will guide you through creating a new agent with a custom system prompt and tool configuration. 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.
#### Flags
| Flag | Description |
| ---------------- | ---------------------------------------------------------------------------------------------------------- |
| `--path` | Directory to write the agent file to (defaults to global or `.opencode/agent` based on the prompt) |
| `--description` | What the agent should do |
| `--mode` | Agent mode: `all`, `primary`, or `subagent` |
| `--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` |
| `--model`, `-m` | Model to use, in `provider/model` format |
Passing all of `--path`, `--description`, `--mode`, and `--permissions` runs the command non-interactively.
--- ---