@mellena1/opencode-auto-mode
v0.5.2
Published
OpenCode v2 plugin that mimics Claude Code auto mode: a cheap LLM auto-approves/denies permission requests, falling back to the user when uncertain.
Readme
opencode-auto-mode
An OpenCode v2 server plugin that mimics Claude Code's auto mode: a cheap LLM reviews each permission request and auto-approves, auto-denies, or falls back to the user when uncertain.
Setup
Add the published package to your global config (
~/.config/opencode/opencode.jsonc):{ "$schema": "https://opencode.ai/config.json", "plugins": [ { "package": "@mellena1/opencode-auto-mode", "options": { "model": { "id": "<cheap-model-id>", "providerID": "<your-provider>" } } } ] }Omit
modelto use your location's default model. Pick something cheap — it runs on every tier-3 permission evaluation.Restart the OpenCode service so the plugin loads:
opencode service restartVerify it's working:
tail -f /tmp/opencode-auto-mode/events.log # look for: "=== plugin loaded (permission.evaluate hook) ==="On Linux logs and decision records live under
/tmp/opencode-auto-mode/(OS temp dir elsewhere). They must stay outside the project — writing inside a watched dir causes config-reload loops.
No extra permissions rules are required: the permission.evaluate hook runs
for allowed decisions too, so the plugin sees requests even without an ask
rule. Keep a fallback rule such as
{ "action": "shell", "resource": "*", "effect": "ask" } if you want prompts
when the plugin is disabled.
The TUI badge loads automatically from the same package (it exports ./tui) —
no separate registration needed.
Options
{
"package": "@mellena1/opencode-auto-mode",
"options": {
"model": { "id": "<cheap-model-id>", "providerID": "<your-provider>" },
"review": "all",
"allowInProjectEdits": true,
"trusted": [...],
"blocks": [...],
"exceptions": [...]
}
}model— cheap LLM used for tier-3 review. Omit to use your location's default model.review—"all"(default) reviews every evaluation that reaches tier 3, including ones OpenCode would silently allow."prompts"reviews only evaluations whose computed effect is alreadyask.allowInProjectEdits—true(default) auto-allowsedit/writewhen every target is inside the session's project directory.falsesends them to the reviewer. When the project directory is unknown, edits go to review.trusted/blocks/exceptions— override the reviewer's policy slots (trust boundary, block rules, narrow carve-outs). Omit for the conservative defaults insrc/policy.ts.
For keybinds.allow / keybinds.deny (manual allow/deny commands), see
TUI status indicator.
How it works
The plugin registers a permission.evaluate hook, which OpenCode runs after
its own permission rules are evaluated but before an action runs or a
permission prompt is published:
permission evaluation (after config rules)
│
▼
classify (tier 1/2/3)
│
┌────┼────────┐
▼ ▼ ▼
auto- auto- LLM review
allow deny (tier 3)
│ │ │
▼ ▼ ▼
effect effect LLM decides
allow deny │
│
┌────┼────┐
▼ ▼ ▼
ALLOW DENY ASK
│ │ │
▼ ▼ ▼
effect effect effect "ask"
allow deny (+ reason shown
in the permission dialog)Because the hook decides before the prompt is published, there is no race with the host's permission dialog: when the plugin allows or denies, no dialog appears at all; when it escalates, the dialog opens with the reviewer's explanation attached.
Tiered review
Inspired by Anthropic's Claude Code auto mode and pi-auto-reviewer:
Tier 1 (auto-allow, instant, no LLM cost): safe read-only commands —
ls,cat,grep,git status,git log,git diff,echo,whoami,pwd,npm list, plusread/glob/grep/websearchactions, etc.edit/writeinside the project directory is also auto-allowed (reviewable via version control); edits outside it, or when the project directory is unknown, go to review.Tier 2 (auto-deny, instant): catastrophic commands —
rm -rf /,sudo,chmod 777,dd,mkfs,shutdown,reboot, fork bombs, etc.Tier 3 (LLM review): everything else → two-stage review. Stage 1 is a cheap BLOCK/ALLOW triage that errs toward blocking (unclear output blocks); only flagged actions pay for stage 2 reasoning, which decides ALLOW, DENY, or ASK. Chained commands (
&&,||,;, pipes, redirects, command substitution) are evaluated as one action. The reviewer sees your recent user messages plus prior tool calls (assistant prose and tool outputs are stripped so injections can't talk it into a bad call) and applies conservative intent rules: related to your goal is not the same as authorized. Subagent delegations (task/subagent) are always reviewed, since the orchestrator's instruction is not your authorization.
By default tier 3 reviews every evaluation that reaches it, including
actions OpenCode's rules would silently allow. Explicit deny rules are
respected as-is (the hook leaves them denied). Set "review": "prompts" to
only review evaluations whose computed effect is already ask — the
lower-cost behavior.
Repeated denials escalate to you as a backstop (3 in a row or 20 total per session, for both tier-2 and reviewer DENY decisions). Approvals and escalations reset the consecutive count.
Tool outputs are also screened for prompt-injection patterns (ignore
previous instructions, curl … | bash, credential exfiltration); matches
get a warning prepended so the agent treats them as untrusted data.
Failure handling
- Up to 3 review attempts with a 1-second delay between attempts and a 30s timeout per attempt (handles startup races where model connections aren't ready yet). Session/project context lookups time out after 3s and continue without that context.
- On all failures, the hook escalates to
askwith a failure note → the user decides.
TUI status indicator
The same package ships a TUI plugin (./tui entrypoint, src/tui.tsx) that
shows what auto-mode is doing while you work. The CLI loads it automatically
from the server plugin — no separate registration needed:
- A small badge in the top-right corner of the screen (above dialogs):
spinner +
auto-mode reviewingwhile the reviewer LLM is thinking, spinner +auto-mode needs your approvalwhen it escalates to you. The badge clears as soon as the permission is answered. allow/denycommands (command palette + keybindings) so you can answer an escalated permission yourself at any time. They are only enabled once a real permission request exists (not while the reviewer is still thinking).
For CLI-only usage against a remote server, register the package in cli.json
so the badge stays active locally:
// ~/.config/opencode/cli.json
{
"plugins": [
{ "package": "@mellena1/opencode-auto-mode", "options": {} }
]
}For local development, point the package field at the local checkout:
{
"plugins": [
{ "package": "/path/to/opencode-auto-mode/src/tui.tsx", "options": {} }
]
}keybinds.allow/keybinds.deny— keybindings for the manual allow/deny commands (defaults:ctrl+alt+a/ctrl+alt+d).
Files
| File | Purpose |
|------|---------|
| src/index.ts | Server plugin: registers permission.evaluate + tool.execute.after (injection screen) hooks, fetches transcript/project context, runs two-stage LLM review with denial backstop |
| src/tui.tsx | TUI plugin: shows review-in-progress / needs-approval status in a top-right badge, plus manual allow/deny commands and keybindings |
| src/tiers.ts | Command classification: auto-allow, auto-deny, or defer to LLM review (incl. in-project fast path) |
| src/reviewer.ts | Two-stage review prompts (triage + reasoning) with intent/block policy, and ALLOW/DENY/ASK parsing |
| src/policy.ts | Default trust boundary, block rules, and allow exceptions (overridable via options) |
| src/transcript.ts | Minimal transcript builder: recent user messages + tool calls only (assistant prose/outputs stripped) |
| src/probe.ts | Heuristic prompt-injection screen for tool outputs |
| src/state.ts | Decision records under the OS temp dir (/tmp/opencode-auto-mode/state.json on Linux) — the channel that feeds the TUI badge during review |
| src/logger.ts | File logger under the OS temp dir (events.log) — must stay outside the project to avoid triggering an infinite config reload loop |
Logs
tail -f /tmp/opencode-auto-mode/events.logLimitations
- Review latency on allowed actions: by default tier 3 reviews every
evaluation, including ones OpenCode would have allowed silently — expect a
reviewer round-trip on those. Use
"review": "prompts"for the lower-cost scope. - Log path: logs and decision records are written under the OS temp dir
(
/tmp/opencode-auto-mode/on Linux) to avoid triggering config reload loops. Writing inside.opencode/or the project root causes the server to detect a file change and reload the plugin in an infinite loop.
Development
bun install
bun run typecheck
bun testTo test local changes, register the checkout directly in your global config:
// ~/.config/opencode/opencode.jsonc
{
"plugins": [
{
// Local directory (not a file path — the server resolves <dir>/index.ts
// and ignores package.json exports). The repo ships that entrypoint.
"package": "/path/to/opencode-auto-mode",
"options": { "model": { "id": "...", "providerID": "..." } }
}
]
}The plugin tracks the published docs at https://opencode.ai/v2/docs/build/plugins and https://opencode.ai/v2/docs/build/plugins/cli.
