OpenCode: a reviewer subagent gated to exactly the git commands you allow
Define a Markdown agent whose bash access is scoped per command (git diff and grep yes, everything else no) and that can never write.
Run this workflow
CI-verified, 2/2 fixtures passing.
Build this with your agent
One copy-paste hands Claude Code, Codex, or Cursor the full recipe, steps included, nothing to fetch.
Intended Use
Anyone who wants a read-only reviewer with surgical bash permissions. CI parses the Markdown agent's YAML frontmatter and asserts edit=deny and a bash map with the catch-all wildcard ordered first then git diff allowed. No key. The review itself is a fenced model step.
Not for
- Putting the specific allows before the "*" wildcard, last match wins so the wildcard must come first or it overrides your allows
- Expecting CI to run the review, only that the permission frontmatter is well-formed
The Stack
Tested Against
opencode@1.17.4opencode.ai/docs (2026-06)ruby@3.x (YAML stdlib)Side effects & data flow
- Network
- none, local only
- Writes
- ./.opencode/agents/review.md
- Credentials
- none required
Prerequisites
- OpenCode installed
- A provider logged in to actually run it
Steps
- 1
Write the gated reviewer agent and validate the frontmatter
Drop review.md in .opencode/agents/ (per project) or ~/.config/opencode/agents/ (global). Keep the "*" catch-all first and the specific allows after it, since the last matching rule wins. CI parses the YAML frontmatter and asserts edit=deny, wildcard-first, and git diff allowed.
mkdir -p .opencode/agents cat > .opencode/agents/review.md <<'MD' --- description: Reviews code without making changes mode: subagent model: provider/your-cheap-model permission: edit: deny webfetch: deny bash: "*": ask "git diff": allow "git log*": allow "grep *": allow --- You are in review mode. Inspect the diff and flag bugs, security issues, and risky changes. Do not modify files. Suggest fixes as comments only. MD ruby -ryaml -e ' lines = File.read(".opencode/agents/review.md").lines i1 = lines.index { |l| l.strip == "---" } abort "BAD: no frontmatter start" if i1.nil? rest = lines[(i1 + 1)..] i2 = rest.index { |l| l.strip == "---" } abort "BAD: no frontmatter end" if i2.nil? fm = YAML.load(rest[0...i2].join) perm = fm["permission"] || {} abort "BAD: edit not deny" unless perm["edit"] == "deny" bash = perm["bash"] || {} abort "BAD: wildcard not first" unless bash.keys.first == "*" abort "BAD: git diff not allowed" unless bash["git diff"] == "allow" puts "config OK: @review frontmatter parses, edit=deny, bash wildcard-first then git diff=allow" ' - 2
Use @review on a diff (the model step, not checked by CI)
Mention @review in a message; it can run git diff/log and grep to inspect, but never write. It suggests fixes as comments only. The review runs the model, so CI never claims its content.
Eval, 2 fixtures
Last passed: verified todayfrontmatter-okcontainstimeout 30s · max $0Expected:
config OK: @review frontmatter parses, edit=deny, bash wildcard-first then git diff=allowclean-exitexit_codetimeout 30s · max $0Expected:
0
Results
The setup that shows off OpenCode's permission system: surgical, per-command bash, not all-or-nothing. The filename becomes the agent name, so this creates @review.
Did this work for you?
Our CI checks the setup runs. You tell us if the whole thing worked. Tell us straight.
Liked this workflow?
Get new verified workflows in WebAfterAI, three issues a week (Tue, Thu, Sat).