OpenCode permissions config: allow all, ask, deny and examples
Updated 24 October 2026
Out of the box, OpenCode runs almost everything without asking. Permission rules change that: each tool call is allowed, sent to you for approval, or blocked. The idea is the same in OpenCode 1 and 2, but the config format and a few defaults changed, so every permissions example in this guide shows both. Run opencode --version if you’re not sure which one you have.
The short answer
| OpenCode 2 | OpenCode 1 | |
|---|---|---|
| Config key | permissions, an ordered list of rules | permission, an object keyed by tool |
| Shell commands | shell | bash |
| Subagents | subagent | task |
| Edit, write and patch | edit | edit |
| Default for most tools | allow | allow |
| Paths outside the project | allowed | ask |
.env reads | ask | ask |
| Which rule wins | The last one that matches | The last one that matches |
| “Allow always” lasts | Saved for the project | The rest of the session |
OpenCode 2 still reads an OpenCode 1 permission object, so an old config keeps working after you upgrade. New rules are easier to reason about in the OpenCode 2 format.
Allow all permissions in OpenCode
To have OpenCode never ask, including for .env files, end your rules with one that allows everything. Because the last matching rule wins, it overrides the default ask rules. In OpenCode 2:
~/.config/opencode/opencode.jsonc{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "*", "resource": "*", "effect": "allow" }
]
}
In OpenCode 1, the permission key takes a single effect for every tool:
~/.config/opencode/opencode.json{
"$schema": "https://opencode.ai/config.json",
"permission": "allow"
}
For one session instead of every session, OpenCode 2 has opencode --auto (and opencode run --auto), which approves everything you haven’t explicitly denied.
The risk is real: the model can then run any shell command, delete or rewrite files outside the project, push to Git and read your secrets without you seeing it first. A web page or file it reads can contain instructions it follows. Do this in a container or a throwaway checkout, or keep a few deny rules after the allow-all rule (section 7 has them) so the worst commands stay blocked.
1. Where the rules go
Permissions live in your OpenCode config, opencode.json or opencode.jsonc (JSONC allows comments and trailing commas). Both versions read the same places:
- Every project:
~/.config/opencode/opencode.json - One project:
opencode.jsonin the project root, or.opencode/opencode.json - Machine-wide, for admins:
/Library/Application Support/opencode/on macOS,/etc/opencode/on Linux,%ProgramData%\opencode\on Windows
Files are merged, not replaced. In OpenCode 2, permission rules from every file are joined into one list: your global file first, then the project files, then the machine-wide file. Since the last matching rule wins, a project’s opencode.json can loosen a rule you set globally. Section 7 shows how to stop that.
Add the schema line to get autocomplete and validation in your editor:
opencode.jsonc{
"$schema": "https://opencode.ai/config.json"
}
2. Allow, ask, deny and the defaults
| Effect | What happens |
|---|---|
allow | Runs without asking |
ask | Stops and waits for your decision |
deny | Blocked; the model is told it can’t do that |
In OpenCode 2, every agent, including ones you create, starts from this policy before your rules are added:
OpenCode 2 default policy[
{ "action": "*", "resource": "*", "effect": "allow" },
{ "action": "read", "resource": "*.env", "effect": "ask" },
{ "action": "read", "resource": "*.env.*", "effect": "ask" },
{ "action": "read", "resource": "*.env.example", "effect": "allow" }
]
So shell commands, edits, web fetches and reads outside the project all run without a prompt. OpenCode 1 is just as permissive, except that it also asks before touching a directory outside the project (external_directory) and when the same tool call repeats three times with identical input (doom_loop).
If you upgraded and miss the outside-the-project prompt, add it back in OpenCode 2:
~/.config/opencode/opencode.jsonc{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "external_directory", "resource": "*", "effect": "ask" }
]
}
3. Writing rules in OpenCode 2
Each rule has three fields: action (which kind of tool call), resource (the path, command, URL or ID it acts on) and effect. Put the broad rule first and the exceptions after it:
opencode.jsonc{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "shell", "resource": "*", "effect": "ask" },
{ "action": "shell", "resource": "git status *", "effect": "allow" },
{ "action": "shell", "resource": "git diff *", "effect": "allow" },
{ "action": "shell", "resource": "git push *", "effect": "deny" }
]
}
The actions you’ll use most:
| Action | Resource it matches |
|---|---|
shell | The command, for example npm test. A chain like a && b is checked as two commands. |
edit | The file path, for the edit, write and patch tools |
read | The file path, relative inside the project, absolute outside it |
glob, grep | The glob pattern, or the regular expression (not the search path) |
webfetch, websearch | The URL, or the search query |
external_directory | A directory outside the project, usually ending in /* |
subagent, skill | The agent ID, or the skill ID |
question | Always * |
<server>_<tool> | *, for an MCP tool |
How matching works:
*matches any run of characters, including/.?matches exactly one. Everything else is literal, and the pattern has to match the whole value.- A shell pattern ending in a space and
*also matches the bare command:git status *matches bothgit statusandgit status --short. - If one call checks several resources, such as a patch touching three files, any
denyblocks it, otherwise anyaskasks. - If no rule matches at all, OpenCode asks.
~and$HOMEare expanded forread,editandexternal_directory, but not forshell, which is matched against the raw command text.- Paths use forward slashes on every system. On Windows, path matching ignores case.
4. The OpenCode 1 format
OpenCode 1 groups rules by tool. A tool can take one effect, or an object of patterns. Inside an object the last matching pattern wins, so "*" goes first:
opencode.json{
"$schema": "https://opencode.ai/config.json",
"permission": {
"bash": {
"*": "ask",
"git status*": "allow",
"git diff*": "allow",
"git push *": "deny"
},
"edit": "ask",
"webfetch": "ask"
}
}
- Keys:
read,edit,glob,grep,bash,task,todowrite,skill,lsp,question,webfetch,websearch,external_directoryanddoom_loop. "*"as a key sets every tool at once, and"permission": "allow"sets everything to one effect.webfetchonly takes a plain effect in OpenCode 1. URL patterns don’t work there.- The old
toolssetting withtrueandfalseis deprecated since v1.1.1. It still loads, but usepermissioninstead.
5. Different rules for each agent
Top-level rules apply to every agent. Rules under an agent are added after them, so they can tighten or loosen the top-level ones for that agent only. They don’t replace the top-level list. In OpenCode 2:
opencode.jsonc{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "shell", "resource": "*", "effect": "ask" }
],
"agents": {
"build": {
"permissions": [
{ "action": "shell", "resource": "npm test *", "effect": "allow" }
]
}
}
}
Using the ID of a built-in agent, as above with build, adds to that agent’s rules. The built-in agents already differ: plan can’t edit project files, explore can only search, read, fetch and run shell commands, and general can’t start more subagents. A subagent uses its own rules, not its parent’s, so a locked-down primary agent can still launch a subagent that is allowed more. Deny the subagent action for that agent if you don’t want that.
In OpenCode 1 the key is agent (singular) and each agent takes a permission object. OpenCode 1 merges it with the global object, and the agent’s rules take precedence.
6. What happens when OpenCode asks
When a rule resolves to ask, the session pauses and shows the request with three choices:
| Choice | OpenCode 2 | OpenCode 1 |
|---|---|---|
| Allow once | Approves this request only | Approves this request only |
| Allow always | Approves it and saves the suggested pattern as an allow rule for this project | Approves matching requests for the rest of the session |
| Reject | Rejects it and every other pending request in that session | Rejects the request |
The tool picks the pattern “Allow always” saves. For shell it’s a command prefix: approving git status --short may save git status *. Some tools suggest *, which approves every future call of that tool in the project, so read the prompt before you pick it. Saved approvals never override a deny.
OpenCode 2 doesn’t write saved approvals into your config file. To see them and remove one you no longer want, use the API command that ships with OpenCode 2:
Terminalopencode api get /api/permission/saved
opencode api delete /api/permission/saved/<id>
To skip prompts entirely, start with opencode --auto (or opencode run --auto "…"). It approves everything that would have asked, and deny rules still apply. In the OpenCode 2 terminal app, "session": { "permissions": "autoaccept" } in ~/.config/opencode/cli.json does the same for every session, so only use it with deny rules you trust.
7. Recipes
A read-only reviewer agent
Save this as .opencode/agents/reviewer.md in the project, or in ~/.config/opencode/agents/ for every project. The front matter takes the same fields as an agent in opencode.json, and the text below it becomes the agent’s instructions:
.opencode/agents/reviewer.md (OpenCode 2)---
description: Reviews changes for correctness and regressions
mode: subagent
permissions:
- action: edit
resource: "*"
effect: deny
- action: shell
resource: "*"
effect: deny
- action: shell
resource: "git diff *"
effect: allow
---
Review the current changes. List findings in severity order with file and line references.
Then ask your main agent to “use the reviewer subagent to review my changes”. The OpenCode 1 version uses a permission map in the front matter:
.opencode/agents/reviewer.md (OpenCode 1)---
description: Reviews changes without editing
mode: subagent
permission:
edit: deny
bash: ask
webfetch: deny
---
Only analyze the code and suggest changes.
Don’t mix the two: OpenCode 2 reads a file that contains permissions as the new format and ignores an old permission key in it, with a warning in the log.
Ask for commands, but let tests and Git reads run
opencode.jsonc (OpenCode 2){
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "shell", "resource": "*", "effect": "ask" },
{ "action": "shell", "resource": "npm test *", "effect": "allow" },
{ "action": "shell", "resource": "npm run lint *", "effect": "allow" },
{ "action": "shell", "resource": "git status *", "effect": "allow" },
{ "action": "shell", "resource": "git diff *", "effect": "allow" },
{ "action": "shell", "resource": "git log *", "effect": "allow" }
]
}
Swap in your own test runner (pnpm test *, cargo test *, go test *). Because chained commands are checked one by one, npm test && curl … still asks about the curl part. In OpenCode 1, put the same patterns under "bash", after "*": "ask".
Never delete files or push
Add these at the end of your rules so nothing after them can allow the command again:
opencode.jsonc (OpenCode 2){
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "shell", "resource": "rm *", "effect": "deny" },
{ "action": "shell", "resource": "git push *", "effect": "deny" }
]
}
A project’s config can still add an allow for those commands after yours. To make a block stick in OpenCode 2, put it in your global config as a policy. Policies only ever block, they run after every rule and saved approval, and a project can’t lift a policy from your global file:
~/.config/opencode/opencode.jsonc{
"$schema": "https://opencode.ai/config.json",
"policies": [
{ "action": "tool.use", "resource": "shell:rm *", "effect": "deny" },
{ "action": "tool.use", "resource": "shell:git push *", "effect": "deny" }
]
}
In OpenCode 1, use "rm *": "deny" and "git push *": "deny" under "bash".
Block secrets instead of asking
opencode.jsonc (OpenCode 2){
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "read", "resource": "*.env", "effect": "deny" },
{ "action": "read", "resource": "*.env.*", "effect": "deny" },
{ "action": "read", "resource": "*.env.example", "effect": "allow" },
{ "action": "edit", "resource": "*.env", "effect": "deny" }
]
}
This covers OpenCode’s file tools. A shell command such as cat .env is a shell call, so keep shell on ask if that matters to you.
8. Gotchas
- Deny lists leak.
rm *doesn’t matchsudo rm,find . -deleteor a script that deletes files, andgit push *doesn’t matchgit -C app push. OpenCode’s own docs recommend a short allowlist with everything else onaskover trying to list every dangerous command. - Order matters.
"*"orshell *placed last overrides every exception above it. - Mixed formats. Old and new keys can sit side by side at the top level of a config, but keep each agent entirely in one format. If both formats set the same thing, the OpenCode 2 value wins.
- Upgrading from OpenCode 1. You lose the outside-the-project prompt. Add the
external_directoryrule from section 2 if you relied on it. - A config change didn’t apply. In OpenCode 2, run
opencode debug configto see which config files were loaded, andopencode reloadafter editing. In OpenCode 1, restart OpenCode.
Answer OpenCode’s prompts from your browser
Once you have ask rules, OpenCode will stop and wait for you, and it only shows the prompt in its own window. Agent Pets brings the prompt to whatever Chrome tab you’re on:
- Each OpenCode session becomes a named pet that walks along the bottom of every page, next to your Claude Code, Codex and omp sessions.
- When OpenCode asks for permission, its pet bounces. Click it to see what it wants to run and answer Allow, Always or Deny.
- In another app, a desktop notification names the pet that needs you, with Allow and Deny on it.
Your rules still decide what gets asked; Agent Pets only answers the prompts OpenCode raises. npx getagentpets init installs its OpenCode plugin for OpenCode 1 and 2. It runs entirely on your computer, and OpenCode’s own prompt keeps working as usual.
Get Agent Pets — was $25, now $6Get Agent Pets — $25
Questions
Where do I put OpenCode permissions?
In opencode.json or opencode.jsonc: ~/.config/opencode/ for every project, or the project root or its .opencode folder for one project. OpenCode 2 reads a permissions array; OpenCode 1 reads a permission object.
Why doesn’t OpenCode ask before running commands?
Its default policy allows almost everything. Only .env reads ask out of the box, plus paths outside the project in OpenCode 1. Add an ask rule for shell (bash in OpenCode 1) to be asked before commands.
How do I stop OpenCode from running git push?
Add a deny rule for git push * as the last shell rule in OpenCode 2, or under bash in OpenCode 1. In OpenCode 2, a tool.use policy in your global config also stops a project config from allowing it again.
How do I allow all permissions in OpenCode?
In OpenCode 2, add { "action": "*", "resource": "*", "effect": "allow" } as the last rule in permissions. In OpenCode 1, set "permission": "allow". For a single run in OpenCode 2, start it with opencode --auto instead.
What does Allow always do in OpenCode?
It approves the request and also approves later requests that match the pattern the tool suggests, such as git status *. OpenCode 2 saves that rule for the project; OpenCode 1 keeps it for the rest of the session. It never overrides a deny rule.
Sources: OpenCode 2 documentation on permissions, config, agents, policies, CLI settings, migrating from V1 and the API reference; OpenCode 1 documentation on permissions and config; opencode --help for v2.0.24.