The problem • Getting started • How it works • Architecture • Performance • Build from source
A fast, daemon-based AI reviewer for Claude Code tool calls. Reduces permission prompts by using Haiku 4.5 to evaluate commands that don't match your explicit allow/deny rules but are still consistent with what you've permitted.
On macOS, shows a translucent floating HUD for approve/deny decisions. Designed for workflows with multiple background agents running — glance at the HUD and approve (Cmd+Enter), deny, or defer (Esc) without switching to a terminal.
Claude Code's permission system gives you allow and deny lists, but anything that doesn't match either becomes an "ask" that interrupts your flow. You could use --dangerously-skip-permissions to avoid this, but then you have no safety net at all. One bad command and your untracked files are gone, your keys are exfiltrated, or your production database is dropped.
cc-tool-reviewer sits between these two extremes. You keep your allow/deny rules, and for everything in the gray area, an AI reviewer decides if the command is consistent with what you've already permitted. If it's not sure, you get a native dialog to approve or deny with full context, not a terminal prompt you have to context-switch to.
curl -sL https://raw.githubusercontent.com/anish749/cc-tool-reviewer/main/install.sh | bashInstalls to ~/.local/bin/. On macOS, also compiles the native approval dialog (requires Xcode command line tools).
To install to a different directory:
curl -sL https://raw.githubusercontent.com/anish749/cc-tool-reviewer/main/install.sh | INSTALL_DIR=/usr/local/bin bashAdd to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|WebFetch|WebSearch|Monitor",
"hooks": [
{
"type": "command",
"command": "nc -U /tmp/cc-tool-reviewer.sock"
}
]
}
]
}
}Do not use nc -w (timeout). The native dialog needs time for user interaction. If the daemon isn't running, nc fails immediately on connect, so there's no hang risk.
By default the daemon routes review through the local claude command-line tool, in one-shot mode using your existing Claude Code login — no API key needed. It requires the claude binary on PATH. Reviews are a little slower this way (CLI cold-start), but they draw on your Claude subscription rather than metered API credits.
Startup is validated: if the claude binary isn't on PATH (and none of the API credentials below are set), the daemon refuses to start instead of failing silently on every review.
Set one of these to call the Anthropic API directly instead of the local CLI:
ANTHROPIC_API_KEY— an API key from the Anthropic ConsoleANTHROPIC_AUTH_TOKEN— a Bearer token, if using an LLM gateway or proxyANTHROPIC_BASE_URL— a gateway/proxy URL (sufficient alone if the gateway needs no token)
ANTHROPIC_API_KEY=sk-ant-... cc-tool-reviewercc-tool-reviewerOr with a custom socket path:
cc-tool-reviewer --socket /tmp/my-reviewer.sockStart the daemon outside of Claude Code (e.g., from a shell alias or launch script), since the hook would interfere with starting it from within a Claude Code session.
Settings are hot-reloaded. No need to restart the daemon when you change your allow/deny rules.
- Claude Code fires the
PreToolUsehook, piping JSON tonc ncforwards it to the daemon via Unix socket (~4ms overhead)- The daemon decides what to do based on the tool type:
Auto-allowed tools. WebFetch and WebSearch are approved instantly with no matching or AI call. The daemon logs the URL/query for visibility.
Bash commands. Checked against your allow/deny rules locally:
- Matches an allow or deny rule → empty response, Claude Code handles it normally
- Compound command (
&&,||,;, multi-line, subshells) → sent to the AI, since prefix matching can't evaluate these - Leading env-var assignments (
FOO=bar go test) are dropped before matching; a command substitution in an assignment value (FOO=$(cmd) go test) is checked as its own command - No match ("ask zone") → calls Haiku 4.5 with your allow list as context
Monitor commands. Monitor runs a shell script (its command field) in the same environment as Bash, so it's checked the same way: the embedded script is matched against your Bash(...) allow/deny rules, and anything unmatched goes to the AI.
AI says "allow" → tool call proceeds, no prompt.
AI says "ask" → on macOS, a translucent floating HUD appears with:
- Conversation title and working directory
- Recent user messages and tool call history
- The command and AI's reason for flagging it
- A feedback text field (sent back to Claude as context when denying)
- Three buttons:
- Approve (Cmd+Enter) — allow the tool call
- Deny — block it, with optional feedback text sent back to Claude
- Later (Esc) — defer to Claude Code's terminal prompt
Keyboard shortcuts let you approve or dismiss without reaching for the mouse. On non-macOS systems, "ask" falls through to Claude Code's terminal prompt.
Simple commands like rg foo match locally. Compound commands containing &&, ||, ;, newlines, or subshells ($(...)) bypass local matching and are always sent to the AI. cd ~/git/x && git log doesn't match Bash(cd:*) in Claude Code's real matcher, but the AI can evaluate each part and recognize both are individually allowed.
Settings are loaded from (and hot-reloaded on change):
$CLAUDE_CONFIG_DIR/settings.json(falls back to~/.claude/settings.json)$CLAUDE_CONFIG_DIR/settings.local.json.claude/settings.json(project-level).claude/settings.local.json(project-level)
If the daemon isn't running, nc fails with a non-zero exit code (but not exit code 2). Claude Code treats this as a no-op and falls through to the normal permission prompt. Nothing breaks.
Claude Code ──stdin──▶ nc -U /tmp/cc-tool-reviewer.sock ──▶ Go daemon
│
┌──────────┴──────────┐
│ 1. Auto-allow? │
│ (WebFetch, │
│ WebSearch) │
│ ↓ no │
│ 2. Local match │
│ against allow/ │
│ deny rules │
│ ↓ no match │
│ 3. Call Haiku 4.5 │
│ via persistent │
│ HTTP/2 conn │
│ ↓ "ask" │
│ 4. Native dialog │
│ (macOS only) │
└──────────┬──────────┘
Claude Code ◀──stdout── nc ◀────────────────────────────────────┘
Claude Code has a built-in prompt hook type that sends tool calls to a model for review. It handles the API connection internally. But:
Built-in prompt hook |
cc-tool-reviewer | |
|---|---|---|
| Fires on | Every matching tool call | Only "ask zone" calls |
rg foo latency |
~700ms (API call) | ~0ms (local match) |
| Prompt context | Generic, user-defined | Injects your actual allow list |
| Compound commands | No special handling | Detects &&, ||, ;, multi-line and routes to AI |
cc-tool-reviewer replicates your allow/deny matching locally and only calls the API for the "ask zone". Most tool calls (~90%) are resolved in under 5ms with no API call.
| Scenario | Latency |
|---|---|
| Auto-allowed (WebFetch, WebSearch) | ~4ms (nc overhead) |
| Local match (allow/deny rule) | ~4ms (nc overhead) |
| API call (cold connection) | ~1000ms |
| API call (warm connection) | ~700ms |
| Daemon not running (fallback) | ~4ms (nc fails fast) |
git clone https://github.com/anish749/cc-tool-reviewer.git
cd cc-tool-reviewer
make installThe Makefile builds the Go daemon and the SwiftUI dialog (on macOS) from source and installs both to ~/.local/bin/. Requires Swift 5.9+ and Xcode command line tools on macOS.