Skip to main content
luke@terminal:/blog$ ls
luke@terminal:/blog$ cat opencode-subagent-permissions-ordering-trap.md

My Subagents Kept Asking Permission for Everything: The Config Ordering Trap

Created: 2026-04-15 | 3 min read

Every time my release-manager subagent tried to run grep or git log, OpenCode prompted me for approval. I had explicit "grep *": allow rules in the config. They weren't working.

This tripped me up for way longer than it should have. (I'm running OpenCode v1.4 — permission behavior may differ in other versions.)

My Config

The release-manager agent handles version bumps, changelogs, and git tagging for my Projex project. Its config in .opencode/agents/release-manager.md had bash permissions like this:

bash:
  "grep *": allow
  "rg *": allow
  "cat *": allow
  "git log -- *": allow
  "git diff -- *": allow
  "git status": allow
  "*": ask

Looks reasonable. Specific commands are allowed, everything else asks. Except every single command was prompting. grep, git log, cat — all asking for permission.

The Problem

I re-read the config about five times. The rules were right there — "grep *": allow — clear as day. I tried shuffling the order around. Tried different wildcard syntax. Nothing worked.

Eventually I went back to the OpenCode permissions docs and actually looked at the examples. Every single one puts the catch-all "*": "ask" at the top. Mine was at the bottom.

Turns out OpenCode permission rules work on a "last matching rule wins" principle.

The "*" wildcard matches everything. Including grep. Including git log. So when OpenCode evaluates git log --oneline -20 against my config, both "git log -- *" and "*" match. Since "*" is listed last, it wins. Result: ask.

Every. Single. Time.

The fix was embarrassingly simple. Move the catch-all to the top:

bash:
  "*": ask
  "grep *": allow
  "rg *": allow
  "cat *": allow
  "git log -- *": allow
  "git diff -- *": allow
  "git status": allow

Now "*" matches first, but then "grep *" matches after and wins because it's the last matching rule. Specific overrides general. The way it should work.

I'd put the catch-all at the bottom out of habit — like a default case in a switch statement. I just... didn't read carefully enough.

The Second Problem

After fixing the ordering, grep worked fine. But git log --oneline -20 still prompted.

I tested a few more variations to narrow it down. git log -- somefile (with the path separator) matched. git log --oneline didn't. The difference clicked pretty fast after that.

The pattern "git log -- *" only matches commands that literally have -- in them. Like git log -- somefile. It doesn't match git log --oneline -20 because --oneline is a flag, not the -- path separator.

I'd been too specific. Changed to:

  "git log *": allow
  "git log": allow
  "git diff *": allow
  "git diff": allow
  "git status *": allow
  "git status": allow

Both forms — with and without arguments. The * wildcard matches zero or more of any character. So "git log *" covers git log --oneline -20. But the space before * is literal. The pattern requires "git log" followed by a space, then anything. A bare git log with no trailing space won't match it. That's why "git log" (no wildcard) is also needed.

The Third Problem

Even after both fixes, it still prompted. I'd been eyeing the tools field suspiciously since the start. The config had both the deprecated tools block and the newer permission block.

tools:
  read: true
  write: true
  edit: true
  bash: true
permission:
  bash:
    "*": ask
    "grep *": allow

The docs say tools.bash: true is equivalent to {"*": "allow"}. Having both tools and permission for the same thing seemed like it could cause conflicts — one saying "allow everything" and the other saying "ask for everything except these." I don't know exactly how OpenCode resolves this internally, but removing the tools block entirely fixed the remaining issues.

The Working Config

The final config for the release-manager agent looks like this:

---
description: Create releases following Projex's version bump, changelog, and git tagging workflow
mode: subagent
temperature: 0.1
permission:
  edit:
    "*": ask
    "packages/core/package.json": allow
    "CHANGELOG.md": allow
    "README.md": allow
    "packages/core/README.md": allow
    "packages/docs/**/*": allow
  bash:
    "*": ask
    "pnpm --filter * build": allow
    "pnpm --filter * lint": allow
    "pnpm --filter * typecheck": allow
    "pnpm --filter * test": allow
    "head *": allow
    "ls *": allow
    "ls -la *": allow
    "find *": allow
    "grep *": allow
    "rg *": allow
    "cat *": allow
    "tail *": allow
    "wc *": allow
    "wc -l *": allow
    "sort *": allow
    "uniq *": allow
    "git log *": allow
    "git log": allow
    "git diff *": allow
    "git diff": allow
    "git status *": allow
    "git status": allow
    "git tag *": allow
  webfetch: deny
  color: success
---

Three separate issues stacked on top of each other. Rule ordering, overly narrow patterns, and deprecated config conflicting with new config.

TL;DR

Three things I learned about OpenCode subagent permissions:

  1. Catch-all goes first. "*": ask needs to be at the top of the permission block. Last matching rule wins.
  2. Patterns need to match actual command syntax. "git log -- *" doesn't match git log --oneline. I had to use "git log *" instead.
  3. Don't mix tools and permission. Removing the deprecated tools field fixed the remaining issues for me.

I fixed the same issues in my documentation-manager agent too. Both agents now run without prompting on every command. Which is how it should have been from the start.