Skip to main content

Auto-Approve (LLM)

Remi can route Claude Code permission prompts through a Large Language Model (LLM) and automatically approve, deny, or escalate each request. The goal is to eliminate notification fatigue for routine approvals (file reads, git status, test runs) while keeping human review for anything ambiguous or dangerous.

Auto-approve is disabled by default and fully opt-in.

How it works

When Claude Code asks for permission to run a tool, a hook fires to the Remi daemon. With auto-approve enabled, the daemon:

  1. Checks your deny list. Any substring match → injects "No" into the PTY, logs DENIED, no notification. Deny always wins.
  2. Checks your allow list. Any substring match → injects "Yes", no notification, no LLM call.
  3. Otherwise calls the configured LLM with the tool name and input. The LLM returns one of three decisions:
    • approve: injects "Yes" into the PTY, Claude continues, no notification.
    • deny: injects "No", Claude is blocked, logs the reason.
    • escalate: falls through to the normal flow — the prompt reaches your phone/browser.

If the LLM is unreachable, times out, or returns an unparseable response, the request escalates to you. Errors never silently block Claude.

Supported backends

Auto-approve uses the OpenAI-compatible chat completions API. Any provider that speaks that API works:

ProviderDefault base URLNotes
Ollamahttp://localhost:11434/v1Recommended: fully local, no network calls
OpenRouterhttps://openrouter.ai/api/v1Routes to Claude, GPT, or any hosted model
Custom URLAny OpenAI-compatible endpointSelf-hosted, vLLM, LiteLLM, etc.

Quick start

  1. Install a local LLM (recommended for privacy):

    ollama pull gemma4:e2b

    Models validated to pass the security test suite: gemma4:e2b and qwen3.5:4b.

  2. Run Remi with auto-approve:

    remi --auto-approve

    The default model is gemma4:e2b via Ollama. Override with --auto-approve-model.

  3. Use Claude Code normally. Routine commands (git status, ls, bun test) get auto-approved in ~2-6 seconds. Write operations escalate to your phone.

CLI flags

--auto-approve                        Enable auto-approve (overrides config)
--no-auto-approve Disable
--auto-approve-model MODEL e.g. gemma4:e2b, qwen3.5:4b, anthropic/claude-3-haiku
--auto-approve-provider PROVIDER ollama | openrouter | custom URL
--auto-approve-api-key KEY Required for OpenRouter and hosted providers
--auto-approve-allow STR Substring allow pattern (repeatable, appends to config)
--auto-approve-deny STR Substring deny pattern (repeatable, appends to config)
--auto-approve-instructions TEXT Natural-language guidance appended to the LLM prompt

Config file

Persistent settings go in ~/.remi/config.toml:

[auto_approve]
enabled = true
provider = "ollama"
model = "gemma4:e2b"
api_key = "" # Required for OpenRouter
base_url = "http://localhost:11434/v1"
timeout = 10 # Seconds; escalates on timeout
log_decisions = true # Log every approve/deny to ~/.remi/remi.log

# User-defined rules. Substring matching.
allow = [
"git status",
"git log",
"git diff",
"bun test",
"bunx biome",
"gh issue",
"gh pr",
"Read", "Glob", "Grep", # Tool names (non-Bash tools) match by exact name
]

deny = [
"rm -rf /",
"sudo ", # Trailing space avoids matching "sudoku"
"curl | sh",
"| bash",
]

# Natural-language guidance appended to the LLM's system prompt
instructions = """
Approve all test runs and linting.
Escalate anything touching .env or secrets/.
"""

Environment variables

REMI_AUTO_APPROVE=true|false
REMI_AUTO_APPROVE_MODEL=<model>
REMI_AUTO_APPROVE_PROVIDER=<name>
REMI_AUTO_APPROVE_BASE_URL=<url>
REMI_AUTO_APPROVE_API_KEY=<key>
REMI_AUTO_APPROVE_ALLOW=pat1,pat2,... # Comma- or newline-separated
REMI_AUTO_APPROVE_DENY=pat1,pat2,...
REMI_AUTO_APPROVE_INSTRUCTIONS=<text>

Priority: CLI flag > env var > config file > built-in default.

Pattern syntax

Allow and deny lists

Plain substring match. No glob, no regex.

For Bash:

  • Pattern "git push" matches any Bash command containing the string git push — including compound commands like cd /foo && git push origin main.
  • Trailing space disambiguates prefixes: pattern "sudo " matches sudo rm -rf / but not sudoku.
  • Pipe patterns match as substrings: "| bash" catches curl ... | bash.

For other tools (Read, Write, Edit, Glob, Grep, WebFetch, etc.):

  • Pattern is the exact tool name. Pattern "Read" matches any invocation of the Read tool.
  • Tool name is not substring-matched against file_path or content — only the name.

Design note: substring matching is intentionally permissive. It solves Claude Code's compound-command limitation (where Bash(git push:*) fails on cd /foo && git push). The deny list is your safety rail: any match short-circuits to deny before the LLM is consulted.

Order of evaluation

1. deny list (substring match)   → deny, log, no LLM call
2. allow list (substring match) → approve, no LLM call
3. LLM → approve | deny | escalate
└─ error/timeout → escalate

Model requirements

Auto-approve decisions are security-sensitive. Tiny models (under 2B parameters) have been observed to approve dangerous commands. Remi ships a test suite of 38 scenarios covering destructive operations, reverse shells, obfuscation, and data exfiltration. Validated results (2026-04):

ModelParamsScoreNote
qwen3.5:4b4.7B38/38Passes all scenarios
gemma4:e2b5.1B38/38Default — noticeably faster than qwen3.5
qwen3.5:2b2.3B37/38Approved sudo su as "standard admin" — borderline
qwen3.5:0.8b873M31/38Unsafe — approved fork bomb, reverse shell, SUID escalation

Use 4B+ parameter models. Smaller models lack the reasoning capacity for security judgment.

Multi-session behavior

Auto-approve is scoped to the main interactive session that owns the Remi daemon's PTY. Events from background subagents and team members are identified by an agent_id field on the hook payload and are dropped before reaching auto-approve or your phone. They never produce phone notifications and never inject 1 into your chat prompt.

If you run multiple Claude Code sessions in different directories, each gets its own Remi daemon and its own auto-approve — they are independent.

Logs

Every decision logs to ~/.remi/remi.log:

[AutoApprove abc12345] Bash: approve (2341ms) - git log is a routine read
[AutoApprove abc12345] Injected "1" into PTY (approved)
[AutoApprove abc12345] DENIED Bash: deny-matched pattern: "rm -rf /"
[AutoApprove abc12345] ERROR Bash: LLM timeout after 10001ms
[Hooks] Dropped subagent PermissionRequest: agent=a1b2c3d4 type=general-purpose tool=Bash

The 8-character tag is the Remi session ID prefix. Grep by tag to isolate one session's activity when multiple daemons are running.

Troubleshooting

No GPU spike / no activity — the daemon may not have locked onto your Claude session. Check for [Hooks] Event bridge active and [Hooks] Transcript from PreToolUse: claude=... in the log. If two Remi daemons run in the same directory, both lock each other out (known limitation).

Auto-approve approves too much — switch to a stricter model, add items to your deny list, or provide tighter instructions.

Auto-approve escalates everything — check that Ollama is running (curl localhost:11434/api/tags) and the model is pulled. Each escalation logs the reason.

Latency is too highgemma4:e2b is the fastest validated model. You can also try smaller models at the cost of safety (see Model requirements).

Privacy

  • Ollama (recommended): all evaluation happens on your machine. Zero network calls.
  • OpenRouter or other hosted providers: the tool name and input are sent to the provider. Reconsider for sensitive commands.

The evaluation prompt contains the tool name and raw tool_input (e.g. the full Bash command or file path). It does NOT contain your Claude Code conversation, code contents, or Remi daemon state.