
I was setting up a custom AI workflow in OpenCode recently, and I ran into a incredibly frustrating permission bypass issue. I wanted to create a dedicated "Planner Agent". Its sole purpose in life was to analyze my codebase and outline a plan. It was absolutely not supposed to touch the code.
To enforce this, I configured the agent's permissions with edit: deny and bash: deny. I thought I had it locked down. But to my surprise, when I ran the agent, it completely ignored my intentions and started executing the code itself.
How? It sneakily bypassed my restrictions by spinning up a subagent to do the heavy lifting for it.
I started digging through the OpenCode documentation (https://opencode.ai/docs/agents/), but couldn't find an immediate answer. I eventually stumbled upon a GitHub issue and a proposed solution via GitHub PR #4773 (https://github.com/anomalyco/opencode/pull/4773). I decided to document this here so I don't forget it in the future, and hopefully, it saves you the same headache.
The core issue in OpenCode was that agent configuration conflated two very different problems:
When you set edit: deny and bash: deny on a planner agent, the LLM realizes it can't do the work directly. Because there was no strict control over the task (subagent spawning) tool, the LLM simply delegated the execution to a default subagent that did have those permissions. It found a loophole.
Thanks to the design proposed in the GitHub PR, we can now cleanly separate these concerns using three specific configuration properties: visible, permission.task, and tools.task.
permission.taskTo stop your planner agent from spawning execution subagents, you must explicitly control its task permissions. This uses the same pattern as permission.bash.
You can use "allow", "ask", or "deny". Crucially, denying a task removes the subagent from the Task tool description in the system prompt, preventing the LLM from hallucinating or attempting to use it.
{
"$schema": "https://opencode.ai/config.json",
"agent": {
"plan": {
"mode": "primary",
"permission": {
"task": {
"*": "deny"
}
}
},
"orchestrator": {
"description": "Main orchestration agent",
"mode": "primary",
"permission": {
"task": {
"*": "deny",
"orchestrator-coder": "allow",
"orchestrator-quality-gate": "ask"
}
}
}
}
}Note: Rules are evaluated sequentially. "*": "deny" should be placed first to act as a default, followed by specific allowances. Wildcards like "orchestrator-*": "allow" are also supported.
visible propertyIf you have internal subagents (like orchestrator-coder) that you don't want cluttering your Tab rotation or agent menu, you can hide them from the human UI without affecting the LLM's ability to spawn them.
Add a visible property (default is true) to the subagent's configuration:
{
"agent": {
"orchestrator-coder": {
"description": "Internal coding subagent for orchestration loops",
"mode": "subagent",
"visible": false
}
}
}Rules for visible:
visible: false hides the subagent from the agent menu.mode: primary or mode: all agents (these are always visible).permission.task).tools.taskIf you want a primary agent to be completely incapable of spawning any subagents whatsoever (which is exactly what I needed for my strict Planner Agent), you can disable the Task tool entirely.
{
"agent": {
"simple-build": {
"mode": "primary",
"tools": {
"task": false
}
}
}
}Here is a complete config.json showing all these features working together to create a robust, safe agent ecosystem:
{
"$schema": "https://opencode.ai/config.json",
"agent": {
"build": {
"mode": "primary",
"permission": {
"task": {
"*": "deny",
"code-reviewer": "allow",
"explore": "allow",
"general": "allow"
}
}
},
"orchestrator": {
"description": "Automated orchestration agent",
"mode": "primary",
"permission": {
"task": {
"*": "deny",
"orchestrator-coder": "allow",
"orchestrator-planner": "allow",
"orchestrator-quality-gate": "ask"
}
}
},
"orchestrator-coder": {
"description": "Internal: Handles coding tasks for orchestrator",
"mode": "subagent",
"visible": false
},
"orchestrator-planner": {
"description": "Internal: Handles planning for orchestrator",
"mode": "subagent",
"visible": false
},
"orchestrator-quality-gate": {
"description": "Internal: Quality verification",
"mode": "subagent",
"visible": false
},
"code-reviewer": {
"description": "Reviews code for best practices and issues",
"mode": "subagent",
"visible": true,
"tools": {
"write": false,
"edit": false,
"task": false
}
}
}
}code-reviewer, explore, and general, but NOT any orchestrator-* subagents.orchestrator-quality-gate requiring user approval ("ask") before spawning.build, orchestrator, and code-reviewer agents in the UI menu. The internal orchestrator subagents are hidden.To wrap up, here is a quick cheat sheet for your OpenCode agent configurations:
| Concern | Config Property | Location | Effect |
|---|---|---|---|
| Hide subagent from human | visible: false | On the subagent | Removes from UI menu and Tab rotation |
| Control LLM subagent spawning | permission.task | On the primary agent | Controls allow, ask, deny for specific subagents |
| Disable Task tool entirely | tools: { task: false } | On the primary agent | Primary cannot spawn ANY subagents |
| Make subagent also a primary | mode: all | On the agent | Available to user in UI AND spawnable by LLM |
This new design cleanly separates human UX from LLM behavior, follows standard config merging patterns, and finally solves the planner agent execution bypass.