DEV Community

Takuya Takahashi
Takuya Takahashi

Posted on

ccgate: Delegate Claude/Codex permission prompts to an LLM (~97% automated for me)

TL;DR

  • ccgate is an OSS CLI that delegates permission prompts in Claude Code and Codex CLI to a separate LLM (Haiku by default).
  • Outcomes are allow / deny / fallthrough. deny returns a reason the agent can act on. Genuinely ambiguous calls bubble back to the user.
  • In my own usage, ~97% of permission prompts get resolved without prompting me, out of ~2,000 prompts/month on Claude Code.
  • Repo: https://github.com/tak848/ccgate

ccgate allowing a safe command and denying curl pipe bash

Why I built it

I use Claude Code and Codex CLI daily, both for personal projects and at work. As I lean on them more, the volume of permission prompts ("Allow this Tool execution?") goes up — and it's tempting to just slap on --dangerously-skip-permissions. But that's terrifying: agents can break local DBs, hit the network, push branches, and so on.

What I actually wanted was a middle ground: let the agent move freely, but block the genuinely dangerous moves and flag the gray-zone ones to me.

The painful part wasn't just clicking "Yes". A few specific patterns kept biting me:

  • Plan-mode Explore agents running shell-fu ($(), &&, ||, pipes) where each subcommand is on the allow-list, but the parser can't decompose the line.
  • Worktree drift: the agent reads the parent checkout while I'm inside repo-worktree1.
  • gh pr view should pass, but gh pr edit and gh api should not. Same dilemma for Bash(git *).
  • Direct python / python3 instead of uv run. One-shot npx / pnpx for a tool that should be a project script.
  • "Don't do that" comments I write 50 times that the agent forgets ten turns later.

The judgment of "OK / not OK" was actually quite consistent — same criteria almost every time. The real problem is asking a tired human to make that judgment 100 times a day. Around the 50th prompt of the day my "Yes" loses meaning. At that point the permission prompt stops being a meaningful safety check. It becomes another tiny interruption I want to clear as fast as possible. That felt like the wrong shape: the dangerous operations still needed attention, but the repeated gray-zone decisions were draining the attention I was supposed to use for them.

GitHub logo tak848 / ccgate

LLM-powered PermissionRequest hook for coding agents (e.g. Claude Code)

ccgate

CI release

A PermissionRequest hook for AI coding tools that delegates tool-execution permission decisions to an LLM (Claude Haiku) based on rules defined in a configuration file.

ccgate in action: a safe echo is allowed while curl ... | bash is denied with a deny_message

Supported targets:

日本語ドキュメント

How it works

Claude Code / Codex CLI (PermissionRequest hook)
  │
  │  stdin: HookInput JSON
  ▼
ccgate
  ├── Load config (~/.claude/ccgate.jsonnet  or  ~/.codex/ccgate.jsonnet)
  ├── Build context (git repo, paths, recent transcript [Claude only])
  ├── Call Claude Haiku API (Structured Output)
  └── stdout: allow / deny / fallthrough
  1. The AI tool invokes ccgate before executing a tool.
  2. ccgate embeds allow/deny rules from the jsonnet config into a system prompt, sends tool info, git context, and (for Claude) recent conversation history to Haiku.
  3. Returns Haiku's decision to the AI tool.

CLI

ccgate                         Read HookInput JSON from stdin (Claude Code hook)
                               Equivalent to 'ccgate claude'. Permanent default — never
                               deprecated, so existing ~/.claude/settings.json entries
                               using

What ccgate is

Both Claude Code and Codex CLI expose a PermissionRequest hook: a hook fired right before the user gets prompted for an approval. ccgate plugs into that hook.

When the user would normally see a permission prompt, ccgate is invoked first. It builds context (tool input, git context, your jsonnet rules) and asks a separate LLM to decide. The LLM returns one of:

Verdict What it does Typical use
allow Pass the tool execution through Read-only inspection, project-defined tests, etc.
deny Reject with a message `curl \
{% raw %}fallthrough Hand back to the user Genuinely ambiguous calls, last-mile confirmation

The deny_message is delivered to the agent, so it can read "use the project script instead of npx" and try again, instead of being stonewalled.

The point isn't to hide the permission system — it's to automate the gray-zone calls that I was already making the same way every time, and reserve human attention for the cases that actually need it.

Claude Code / Codex CLI
  -> static rules decide?
     -> yes: pass / block as usual
     -> no: PermissionRequest hook
            -> ccgate
            -> tool_input + git context + jsonnet rules
            -> Haiku verdict
               -> allow: execute
               -> deny: reject with reason
               -> fallthrough: back to user prompt
Enter fullscreen mode Exit fullscreen mode

Setup (Claude Code)

Install via mise:

mise use -g aqua:tak848/ccgate
Enter fullscreen mode Exit fullscreen mode

Register the hook in ~/.claude/settings.json:

{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "",
        "hooks": [
          { "type": "command", "command": "ccgate claude" }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Set the API key for your provider. Anthropic is the default, but OpenAI and Gemini are supported too:

export CCGATE_ANTHROPIC_API_KEY=...
# or: export CCGATE_OPENAI_API_KEY=...
# or: export CCGATE_GEMINI_API_KEY=...
Enter fullscreen mode Exit fullscreen mode

It runs on built-in defaults out of the box. To customize:

ccgate claude init   > ~/.claude/ccgate.jsonnet
ccgate claude init -p > .claude/ccgate.local.jsonnet   # untracked, project-local
Enter fullscreen mode Exit fullscreen mode

Provider config is a single block:

{
  provider: {
    name: 'openai',
    model: 'gpt-5.4-nano-2026-03-17',
  },
}
Enter fullscreen mode Exit fullscreen mode

If you override provider, restate the whole block. That means name, model, optional base_url, and optional timeout_ms.

This is intentional. When switching from Anthropic to OpenAI, ccgate should not inherit stale provider fields. That avoids carrying over an old model name or proxy URL from a lower config layer. provider.base_url can point ccgate at compatible proxies. Examples include LiteLLM, Azure OpenAI, on-prem gateways, or regional endpoints.

For Codex CLI, use ccgate codex, ccgate codex init, ccgate codex metrics. Codex hooks are still experimental and gated behind a feature flag. Set [features] codex_hooks = true in ~/.codex/config.toml.

Register the PermissionRequest hook in either ~/.codex/hooks.json or inline [hooks] tables in ~/.codex/config.toml. Project-local .codex/ hooks load once the project is trusted. See the Codex hooks docs for the schema.

The default model is Haiku 4.5; you can swap to Sonnet 4.6 etc. via config. I run Haiku and it costs me ~$5/month with negligible quality issues.

Rules in (almost) plain English

Configuration is jsonnet — a JSON superset, so you can also write plain JSON if you prefer (a pure-JSON loader PR would be welcome). Rules are written as natural-language statements:

{
  environment: [
    'This repository is a trusted local development repository.',
    'Use project-defined task runners such as make, pnpm, mise, or task when available.',
  ],

  allow: [
    'Read-only inspection commands inside the current repository are allowed.',
    'Running project-defined lint, test, build, and format commands is allowed.',
    'Read-only GitHub CLI operations such as viewing pull requests are allowed.',
  ],

  deny: [
    'Do not run one-shot remote package execution such as npx, pnpx, bunx, or curl | bash. deny_message: Direct remote execution is not allowed. Use a project-defined script.',
    'Do not access sibling checkouts when running inside a git worktree. deny_message: Use a path inside the current worktree.',
    'Do not force push or push directly to protected branches. deny_message: Destructive git operation; not auto-approved.',
  ],
}
Enter fullscreen mode Exit fullscreen mode

The work isn't writing a clever LLM prompt — it's writing down the rules you've been applying in your head all day.

Why not just use Auto Mode or Codex auto_review?

This is the question I get most often, so it's worth being concrete. Both products ship something in this space, and ccgate exists because they don't quite cover what I wanted.

What Auto Mode actually does

Auto Mode is a Claude Code permission mode where a separate classifier evaluates tool calls before you'd normally see a prompt. Per the engineering write-up and the Permission modes docs, it has a few specific behaviors worth knowing:

  • Read-only operations and edits inside the working directory get auto-approved without going through the classifier.
  • Broad Bash(*)-style allow rules are dropped — Auto Mode does not honor them, by design.
  • After 3 consecutive blocks (or 20 total) the classifier hands control back to the regular permission prompt.
  • It runs on Max / Team / Enterprise / API; Pro is excluded; Anthropic API only (no Bedrock / Vertex / Foundry); model is restricted (Opus 4.7 on Max; Sonnet 4.6 / Opus 4.6 / Opus 4.7 on Team / Enterprise / API).
  • You can keep Auto Mode active during Plan mode via useAutoModeDuringPlan (default true).

The "broad allow rules dropped + classifier + N-block fallback" combination is something only the integrated tool can really pull off cleanly. If your plan and model fit, try it. ccgate is not a replacement.

What Codex auto_review actually does

Codex CLI doesn't have a permission mode with the same name. What it has is auto_review, enabled in config.toml:

approvals_reviewer = "auto_review"

[auto_review]
policy = """
... your local reviewer policy instructions ...
"""
Enter fullscreen mode Exit fullscreen mode

Eligible interactive approval requests get routed to an automated reviewer. [auto_review].policy lets you append local reviewer instructions; in managed environments, guardian_policy_config takes precedence over the local policy. It's reviewer-style, internal to Codex — rules, judgment, and logs all live inside Codex itself.

Why I still keep ccgate on

Aspect Auto Mode Codex auto_review ccgate
Where it lives Built into Claude Code Built into Codex CLI External PermissionRequest hook
How you opt in Switch to Auto Mode approvals_reviewer = "auto_review" Register a hook command
Verdicts Allow / block (with fallback after N blocks) Allow / deny allow / deny / fallthrough
deny_message you control Limited [auto_review].policy (Codex-internal) Full — written in your jsonnet rules
Rule format autoMode.environment / allow / soft_deny policy (Codex-internal format) jsonnet — same shape covers both CLIs
Logs / metrics Claude Code's UI and logs Codex-internal Plain JSONL on disk; ccgate <target> metrics
Coverage Claude Code only Codex CLI only Both, with libsonnet for shared bits
Plan mode Works via useAutoModeDuringPlan N/A Hook fires regardless of mode, so it just works

The bullets I actually care about:

  • deny_message flexibility. A plain "no" makes an agent try a nearby bad command again. A concrete message like "use the project-defined script instead of npx" gives it a path to recover. I want to keep authoring that text.
  • Same shape across both CLIs. I use Claude Code and Codex CLI both. With Auto Mode + auto_review, I'd be maintaining two separate rule formats inside two separate harnesses. With ccgate, the same jsonnet covers both, and shared rules go into a libsonnet file imported from each side.
  • Project-local layering. Per-repo rules layer on top of my personal defaults via append_allow / append_deny / append_environment.
  • Everything readable on disk. Prompt, config, logs, metrics — all in plain files in my dotfiles. When something feels off, I can grep the JSONL and see the exact verdict and reason.
  • Explicit fallthrough. Genuinely ambiguous calls go back to the normal permission prompt instead of being forced into allow/deny. The classifier doesn't have to pretend to be sure.
  • Plan mode coverage as a side effect. Because it's a hook, not a mode, the gate runs the same way whether I'm in default, Plan, or anything else. With Plan-mode Explore agents that chain rg / find / git through && and $(), that's a real difference in feel.

The way I'd describe it: the loop I was already running by hand — see command → check it → decide — got externalized into a transparent hook with rules, prompt, and logs all visible to me. Auto Mode and auto_review are both well-built; ccgate just sits in a different place in the stack, and that placement is what I happen to want.

So this is not "Auto Mode is bad". It's "I wanted an explicit, inspectable, cross-tool permission gate that I can tune myself."

Numbers

In my environment, between 2026-04-01 and 2026-04-28, ccgate handled 2,079 PermissionRequest-class events for Claude Code:

Metric Count
Total 2,079
Allow 1,764
Deny 190
Fallthrough 113
Error 11
Automation rate 94.0%

The 94% includes development noise (rule churn, intentional misfires while testing). On normal days the ratio sits at 97%–close to 100%. Average latency is ~3 seconds, which is roughly the same time I'd take to read a long shell command — except this time it's actually being read.

Denies in my logs include: edits outside the repo, direct PR-body edits, commands run from outside the working directory. Fallthroughs include: local DB migrate-down / drop, draft-PR final publish steps. The "convenient AND it's actually catching things" combination is what I was after.

Caveats

  • ccgate is not a security boundary. Hard limits (permissions.deny, sandboxing, managed settings) should still do that job.
  • It costs API calls. Manageable in interactive use but worth watching for high-volume unattended runs.
  • Plan-mode "correctness" depends on prompts. Not a hard guarantee. Tracked as Issue #37.
  • Auto Mode, Claude Code hooks, and Codex hooks are evolving fast. Always check the live docs.

Closing

If permission prompts have you tired but --dangerously-skip-permissions feels like jumping off a cliff, this might be the in-between you want.

Repo: https://github.com/tak848/ccgate

Feedback and PRs welcome.

Top comments (0)