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.
Related workflows
- One AGENTS.md, no drift: prove CLAUDE.md is a real symlink and the generated files are in sync
- Self-host CodeWiki on private code: validate the config before you spend tokens
- Run GLM-5.2 for the bulk, escalate the hard turns to Opus 4.8
- Teach OpenCode Go your weekly chore once, then run it in minutes
- codebase-memory-mcp: wire the knowledge graph, stop re-reading files
- OrcaRouter for coding: judge by passing tests, not by vibes
Liked this workflow?
Get new verified workflows in WebAfterAI, three issues a week (Tue, Thu, Sat).