Auto Approval
opencode plugin that auto-approves tool permission requests based on configurable rules
3
近 30 天 +2
608
近 7 天 251
45.4
生态多维模型
19 小时前
2026-10-04
快速安装与配置
opencode.json写入当前项目的 opencode.json,只对这个仓库生效。
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-auto-approval-plugin@0.5.0"]
}写入 ~/.config/opencode/opencode.json,对所有项目生效。
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-auto-approval-plugin@0.5.0"]
}若你要在本地改造这个插件,先装到项目里再从本地路径引用。
shell
pnpm add -D opencode-auto-approval-pluginOpenCode 启动时会通过内嵌运行时自动加载 npm 依赖并缓存至本地目录,无需手动在全局环境执行安装。
An OpenCode plugin that sends tool operations to a read-only AI reviewer before automatically approving them.
By default the reviewer runs in its own OpenCode session. It may inspect the workspace with read,
glob, grep, and lsp, but cannot edit files, run shell commands, access the network, use MCP
tools, or start subagents. Alternatively, decisions can be delegated to the
decision model over HTTP — TypeSafe AI's Jev or Cloudflare's Clef.
Supported OpenCode versions
The package ships both plugin API generations in one default export, so the same version works on:
| OpenCode | Plugin API | Config key |
|---|---|---|
| 2.x | V2 (@opencode/plugin, setup()) |
plugins |
| 1.18.29 – 1.x | V1 (@opencode-ai/plugin, server()) |
plugin |
OpenCode releases before 1.18.29 only accept a bare function as the plugin export and cannot load
this package; use opencode-auto-approval-plugin@0.1.x there.
Install
OpenCode installs npm plugins listed in its configuration automatically. Add the package to the project or global OpenCode configuration.
OpenCode 2.x (opencode.json):
{
"$schema": "https://opencode.ai/config.json",
"plugins": ["opencode-auto-approval-plugin"],
}
OpenCode 1.x:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-auto-approval-plugin"],
}
For local development, build the package and place it under .opencode/plugins/ (2.x) or
.opencode/plugin/ (1.x), or link the package through an npm workspace. OpenCode also loads
TypeScript files placed directly in those directories.
Configuration
The defaults are mode: "on-ask", a 30-second review timeout, the provider/model of the main
session, and no custom review instructions.
OpenCode 2.x passes options through a { "package", "options" } entry:
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
{
"package": "opencode-auto-approval-plugin",
"options": {
"mode": "on-ask",
"reviewer": {
"timeoutMs": 30000,
},
},
},
],
}
OpenCode 1.x uses a plugin tuple instead:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
[
"opencode-auto-approval-plugin",
{
"mode": "on-ask",
"reviewer": {
"timeoutMs": 30000,
},
},
],
],
}
Set reviewer.model to run reviews through a separately configured OpenCode provider and model.
With the default opencode backend the plugin never reads or manages API keys; authentication
remains entirely in OpenCode.
{
"plugins": [
{
"package": "opencode-auto-approval-plugin",
"options": {
"mode": "all-tools",
"reviewer": {
"model": {
"providerID": "openrouter",
"modelID": "openai/gpt-5.6-luna",
},
"timeoutMs": 15000,
},
},
},
],
}
Decision model reviewer backend
Set reviewer.backend to "decision-model" to have a decision model judge each operation instead
of an OpenCode session. The plugin sends one request with the operation (source, action,
resource, and the user's latest prompt) and a single allow / deny / escalate choice. No
reviewer agent or session is created and the workspace is not inspected. Two providers speak the
same System One API:
| Provider | Models | Credentials (option / environment) | Default model |
|---|---|---|---|
typesafe |
TypeSafe AI's Jev (jev-latest, jev-1.13.0, …) |
apiKey / TYPESAFE_API_KEY, optional baseURL / TYPESAFE_BASE_URL |
jev-latest |
cloudflare |
Cloudflare's Clef on Workers AI (clef, clef-flash) |
apiKey / CLOUDFLARE_API_TOKEN, accountId / CLOUDFLARE_ACCOUNT_ID |
clef |
{
"plugins": [
{
"package": "opencode-auto-approval-plugin",
"options": {
"mode": "on-ask",
"reviewer": {
"backend": "decision-model",
"timeoutMs": 10000,
"decisionModel": {
"provider": "cloudflare", // or "typesafe"
"model": "clef",
"minAllowProbability": 0.6,
},
},
},
},
],
}
| Option | Default | Description |
|---|---|---|
reviewer.backend |
"opencode" |
"opencode" (reviewer session) or "decision-model" |
reviewer.decisionModel.provider |
— (required) | "typesafe" or "cloudflare" |
reviewer.decisionModel.apiKey |
from the environment | TypeSafe AI API key, or a Cloudflare API token with Workers AI access |
reviewer.decisionModel.baseURL |
https://api.typesafe.ai |
typesafe only: API origin; a bare HTTPS origin (HTTP only for loopback) |
reviewer.decisionModel.accountId |
from the environment | cloudflare only: the 32-character account ID |
reviewer.decisionModel.model |
per provider (above) | Model or alias; pin a version such as jev-1.13.0 for stable behavior |
reviewer.decisionModel.minAllowProbability |
0.6 |
An allow answered with a lower probability becomes escalate |
- Prefer the environment variables:
opencode.jsonis often committed, and a key written there is shared with it. Surrounding whitespace in keys is trimmed. The plugin fails at startup when the provider has no key (or, for Cloudflare, no valid account ID or model name) or an invalid base URL. The variables are read by the process that loads the plugin: if OpenCode 2.x's background service was already running, runopencode service restartafter exporting them. typesafe: the key and the base URL come from the same place. Withreviewer.decisionModel.apiKey, onlyreviewer.decisionModel.baseURLapplies (TYPESAFE_BASE_URLis ignored); withTYPESAFE_API_KEY, onlyTYPESAFE_BASE_URLapplies, and settingreviewer.decisionModel.baseURLwithout a key next to it fails at startup. This keeps one source from redirecting a key supplied by another.cloudflare: requests always go tohttps://api.cloudflare.com/client/v4/accounts/<accountId>/ai/run/@cf/cloudflare/<model>; there is no base URL option, so the token never reaches another host. The variable names match wrangler's, but aCLOUDFLARE_API_TOKENexported for deployments is often broad — create a token scoped to Workers AI for the reviewer. AI Gateway is not supported yet.- A decision model returns a choice with calibrated probabilities rather than an explanation, so
the verdict reason reads like
clef chose deny (allow 0.01, deny 0.86, escalate 0.13).A hesitantallowbelowminAllowProbabilityis escalated to a human;denyandescalateare taken as answered. - Measured from a devcontainer on 2026-10-04, a review took 0.2–0.9 s on either Clef model. Both
Clef models allowed
pnpm testand denied sending.envto a remote host, butclef-flashfollowed custom review instructions only weakly (a force-push declared safe rose to allow 0.53, under the default threshold, whereclefreached 0.80), which is whyclefis the default. - The operation leaves your machine: the resource holds the full command, file content of a write or edit, and permission metadata such as diffs, and the user intent is your latest prompt. All of it is sent to the provider, so an edit of a secrets file sends those secrets.
- A resource larger than 64,000 characters once encoded as JSON (for example a large file write)
is sent as a truncated preview, and a prompt longer than 16,000 characters is cut; an
allowfor either is escalated, because the model saw only part of it. Withon-askthat leaves OpenCode's permission prompt; withall-toolssuch a tool call is always blocked, and retrying the same call does not help — split the write or switch toon-ask. baseURLmust use HTTPS unless it points at a loopback host (localhost,127.0.0.1,[::1]). Whoever serves it receives the API key and decides every verdict, so set it only in configuration you trust (not a repository'sopencode.jsonyou have not reviewed) — the same holds forminAllowProbability, which lowers the bar for an automatic approval.- Redirects are refused so the API key is never forwarded to another host, and the timeout covers
the whole request including the response body. HTTP errors (
402out of credit,429rate limited,5xxoutage) are reported by status only and handled like any other reviewer failure. reviewer.modelhas no effect with thedecision-modelbackend, andreviewer.decisionModelnone withopencode. Aliases such asjev-latestfollow new model releases, which may shift verdicts; pin a version for stable behavior.- Deprecated:
backend: "jev"withreviewer.jev(apiKey,baseURL,model,minAllowProbability) from v0.3 still works and meansdecision-modelwith thetypesafeprovider. Combining it withreviewer.decisionModelfails at startup, and so doesreviewer.jevnext tobackend: "decision-model".
Usage and cost statistics
Each decision model review appends one line to a usage log at
$XDG_DATA_HOME/opencode-auto-approval-plugin/usage.jsonl (~/.local/share/… when XDG_DATA_HOME
is unset): the time, provider, model, a hash of the project directory, input and output tokens,
latency, the verdict (error for a failed call), and the cost at the time of the review. The
operation, your prompt and the reason are never written, and the file is created readable by you
only. The project hash keeps the path out of the file but is not a secret — anyone who guesses a
path can hash it and match it — so treat the log as private before sharing it. Reviews with the
opencode backend run in OpenCode sessions, so opencode stats already counts them.
Show the totals with the bundled command, modelled on opencode stats:
npx opencode-auto-approval-plugin stats # this calendar year so far
npx opencode-auto-approval-plugin stats --days 7 # today and the 7 days before (0 = today)
npx opencode-auto-approval-plugin stats --year 2026
npx opencode-auto-approval-plugin stats --all --project . --json
auto-approval stats · today and the 7 days before · all projects
reviews 1,284 tokens 612k in / 51k out cost $0.11
provider model reviews tokens in tokens out cost p50 latency
cloudflare clef 904 431k 0 $0.10 412 ms
typesafe jev-latest 380 181k 51k $0.00760 212 ms
verdicts allow 81% · escalate 15% · deny 3% · error 1%
- Costs use the input prices published on 2026-10-04 (USD per million tokens: Jev $0.042, Clef
$0.24, Clef-flash $0.09; output tokens are free) and apply to the official endpoints only. A
model without a known price, or a review sent to a custom
baseURL, is counted but left out of the cost, and the summary says how many reviews that was. The latency column is the median. - Set
reviewer.recordUsagetofalseto stop writing the log. A failure to write it never affects a review.
Custom review instructions
Set reviewer.instructions to tell the reviewer about your own policy, such as tool uses that are
always safe in your project or operations that must always go to a human. Give one string or a list
of strings; a list is joined into one line per entry, which is easier to read in JSON than one long
string.
{
"plugins": [
{
"package": "opencode-auto-approval-plugin",
"options": {
"reviewer": {
"instructions": [
"`pnpm test`, `pnpm lint` and `pnpm typecheck` are always safe in this project.",
"Reading and editing files under `src/` and `docs/` is safe.",
"Always escalate `git push` and anything that touches `.env` files.",
],
},
},
},
],
}
- Both backends receive the instructions as trusted guidance that takes precedence over the
built-in safety guidance — though never over the answer format or the rule that operation data
is untrusted, so text inside a command or file cannot pose as your instructions: the
opencodereviewer reads them in its prompt ahead of the operation data, and thedecision-modelbackend appends them to the question'sinstructions, never to the state it judges. - They are guidance for an AI reviewer, not deterministic rules: the reviewer still sees the whole
operation and may decide otherwise. Use OpenCode's own permission rules (
permissionson 2.x,permissionon 1.x) when a tool must always be allowed or denied. Explicit OpenCodedenyrules still always win. - Blank entries are ignored, and the joined text may be at most 4,000 characters. With the
decision-modelbackend the instructions are sent, and billed, with every review. - Instructions can widen what is approved automatically, so set them only in configuration you
trust, like
baseURLandminAllowProbability— not in a repository'sopencode.jsonyou have not reviewed.
Review modes
| Mode | Reviewed operations | allow |
deny |
escalate / reviewer failure |
|---|---|---|---|---|
on-ask (default) |
Only operations that OpenCode already decided should ask | Approves the request once | Leaves the OpenCode approval pending | Leaves the OpenCode approval pending |
all-tools |
Every intercepted tool call, including OpenCode-allowed calls | Runs the tool | Blocks the tool | Blocks the tool and reports that human review is required |
On OpenCode 2.x, on-ask runs inside the permission.evaluate hook: an allow verdict turns the
pending ask into allow before the permission prompt is shown, and the reviewer's reason is
attached as the permission message. On OpenCode 1.x the plugin listens for the permission bus
event and replies once through the SDK. In both cases anything other than allow leaves
OpenCode's native human permission UI untouched.
OpenCode's plugin API does not provide a way to create and await a new permission dialogue from
tool.execute.before. Therefore, all-tools fails closed for an escalate verdict: the tool does
not run and the user must explicitly retry after reviewing the reported reason.
Both modes work the same way with either reviewer backend, except that the decision-model backend always escalates an operation too large to send in full (see above).
Explicit OpenCode deny rules always remain in effect. The plugin is an additional review layer;
it never turns a built-in deny into an allow.
Toolchain
| Area | Tool | Config |
|---|---|---|
| Runtime / tooling | mise | mise.toml |
| Package manager | pnpm | pnpm-workspace.yaml, .npmrc |
| Language | TypeScript | tsconfig.json |
| Build | tsdown | tsdown.config.ts |
| Test | Vitest | vitest.config.ts |
| Format | oxfmt | .oxfmtrc.json |
| Lint | oxlint | .oxlintrc.json |
| Unused code | knip | knip.ts |
| Spelling | cspell | cspell.json |
| Secret scanning | secretlint | .secretlintrc.json |
| Git hooks | simple-git-hooks + lint-staged | package.json, .lintstagedrc.js |
| AI rules | rulesync | rulesync.jsonc, .rulesync/ |
| Workflow lint | actionlint | .github/workflows/actionlint.yml |
| Action pinning | pinact | .pinact.yaml, .github/workflows/pinact.yml |
| Dependency bumps | Dependabot | .github/dependabot.yml |
| Misconfig scan | Trivy | .trivyignore, .github/workflows/trivy-security-scan.yml |
| Dev environment | Dev Container | .devcontainer/ |
| CI / Release | GitHub Actions | .github/workflows/ci.yml, publish.yml |
Getting started
mise install # install node, pnpm, actionlint, pinact
pnpm install # install dependencies and set up the pre-commit hook
pnpm cicheck # run everything CI runs
Scripts
| Script | Description |
|---|---|
pnpm build |
Build the library (ESM + CJS, types) and the stats bin into dist |
pnpm check |
fmt:check + oxlint + typecheck |
pnpm cicheck |
cicheck:code + cicheck:content — what CI runs |
pnpm cicheck:code |
check + test |
pnpm cicheck:content |
cspell + secretlint |
pnpm fix |
Auto-fix formatting and lint problems |
pnpm generate |
Regenerate AI tool configs from .rulesync/ |
pnpm knip |
Report unused files, exports, and dependencies |
pnpm test |
Run the test suite |
pnpm test:coverage |
Run the test suite with coverage |
pnpm typecheck |
Type-check without emitting |
mise tasks
| Task | Description |
|---|---|
mise run actionlint |
Lint GitHub Actions workflows |
mise run pinact |
Pin actions in workflows to full commit SHAs |
mise run pinact:check |
Fail if any action is not pinned to a commit SHA |
mise run trivy |
Scan .devcontainer/ and workflows for misconfigurations |
Supply chain hardening
.npmrcsetssave-exact=true, so every dependency is pinned to an exact version.pnpm-workspace.yamlsetsminimumReleaseAge: 1440, so a version published less than a day ago is refused — a compromised release has time to be pulled before it reaches a lockfile.- Postinstall scripts are blocked by default via
allowBuilds; add a package there only when a build step is genuinely required. CI installs with--ignore-scripts. - Every third-party GitHub Action is pinned to a full-length commit SHA, enforced by
pinactin CI. - Workflows declare the narrowest
permissions:block they need. secretlintruns over every staged file through lint-staged, and over the whole tree in CI.trivy configscans.devcontainer/and.github/workflows/for misconfigurations on every push and pull request that touches them;CRITICALandHIGHfindings fail the build. Suppressions live in.trivyignore, each with the reason it is safe.- The dev container pins the Codex CLI installer to a version and verifies its SHA-256 checksum before running it.
Dev container
.devcontainer/ provides a sandboxed environment for running AI coding agents with relaxed
permissions. It is adapted from dyoshikawa/rulesync and
ships Node, mise-managed tooling (including actionlint and pinact), gh, Claude Code, Codex
CLI, opencode, Gemini CLI, git-gtr, and zsh/bash with completions.
Open the repository in a Dev Container-aware editor and it builds from .devcontainer/Dockerfile,
then runs .devcontainer/init.sh to configure git credentials, the pnpm store, and pnpm install.
Secrets are read from the host environment, so export the ones you need before opening the container — all of them are optional:
| Host variable | Forwarded as |
|---|---|
OPENCODE_AUTO_APPROVAL_PLUGIN_DEVCONTAINER_GITHUB_TOKEN |
GITHUB_TOKEN |
OPENCODE_AUTO_APPROVAL_PLUGIN_DEVCONTAINER_OPENAI_API_KEY |
OPENAI_API_KEY |
OPENCODE_AUTO_APPROVAL_PLUGIN_DEVCONTAINER_GEMINI_API_KEY |
GEMINI_API_KEY |
OPENCODE_AUTO_APPROVAL_PLUGIN_DEVCONTAINER_OPENROUTER_API_KEY |
OPENROUTER_API_KEY |
OPENCODE_AUTO_APPROVAL_PLUGIN_DEVCONTAINER_ZAI_API_KEY |
ZHIPU_API_KEY |
OPENCODE_AUTO_APPROVAL_PLUGIN_DEVCONTAINER_OPENCODE_API_KEY |
OPENCODE_API_KEY |
mise.toml is copied into the image at build time, so changing it requires rebuilding the
container.
AI coding agent rules
Rules live in .rulesync/ and are compiled into each tool's native format by pnpm generate:
.rulesync/rules/*.md— instructions (overview, coding, testing, GitHub Actions security).rulesync/mcp.json— MCP servers.rulesync/hooks.json— session hooks.rulesync/permissions.jsonc— per-tool permission settingsrulesync.jsonc— which tools to generate for (Claude Code, Codex CLI, GitHub Copilot, opencode)
Generated files (AGENTS.md, CLAUDE.md, .claude/, .github/instructions/, …) are gitignored —
edit .rulesync/** instead, never the generated output.
Publishing
.github/workflows/publish.yml publishes to npm when a GitHub Release is published, or when run
manually for a release tag. It checks that the tag is a semantic v*.*.* version, matches
package.json, and points to a commit in main; it then runs pnpm cicheck, builds, and publishes
through npm Trusted Publishing (OIDC — no npm token in
secrets).
Configure npm's trusted publisher for dyoshikawa/opencode-auto-approval-plugin to use GitHub
Actions and the .github/workflows/publish.yml workflow. For each later release, bump the package
version on main, create its matching v<version> tag, and publish the GitHub Release.
OpenCode publishes and distributes plugins as ordinary npm packages: users add the package name to
the plugins (2.x) or plugin (1.x) array in opencode.json, and OpenCode installs it at startup.
See the OpenCode plugin documentation and the
V1 migration guide for the loader and
cache behavior.
License
同类生态推荐
Tell Sessions
opencode-tell-sessions
Inter-session direct messaging (DM) for OpenCode V1 & V2: agents in different sessions can message each other
Permission Reviewer
opencode-permission-reviewer
Policy-aware permission reviewer for OpenCode V1 and V2
Model Fallback
@smart-coders-hq/opencode-model-fallback
OpenCode plugin for ordered model fallback chains, preemptive redirect, and automatic rate limit recovery.