<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Takuya Takahashi</title>
    <description>The latest articles on DEV Community by Takuya Takahashi (@tak848).</description>
    <link>https://dev.to/tak848</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3887307%2F82f2f1f8-e880-490b-b82c-275ab1a430c4.jpeg</url>
      <title>DEV Community: Takuya Takahashi</title>
      <link>https://dev.to/tak848</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tak848"/>
    <language>en</language>
    <item>
      <title>ccgate: Delegate Claude/Codex permission prompts to an LLM (~97% automated for me)</title>
      <dc:creator>Takuya Takahashi</dc:creator>
      <pubDate>Fri, 01 May 2026 14:43:11 +0000</pubDate>
      <link>https://dev.to/tak848/ccgate-delegate-claude-code-codex-cli-permission-prompts-to-an-llm-274c</link>
      <guid>https://dev.to/tak848/ccgate-delegate-claude-code-codex-cli-permission-prompts-to-an-llm-274c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TL;DR&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgo0pnw7g2z0tvoq0ewib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgo0pnw7g2z0tvoq0ewib.png" alt="ccgate allowing a safe command and denying curl pipe bash" width="758" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt;. But that's terrifying: agents can break local DBs, hit the network, push branches, and so on.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The painful part wasn't just clicking "Yes". A few specific patterns kept biting me:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tak848" rel="noopener noreferrer"&gt;
        tak848
      &lt;/a&gt; / &lt;a href="https://github.com/tak848/ccgate" rel="noopener noreferrer"&gt;
        ccgate
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      LLM-powered PermissionRequest hook for coding agents (e.g. Claude Code)
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;ccgate&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/tak848/ccgate/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/tak848/ccgate/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://github.com/tak848/ccgate/releases" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/tak848/ccgate/actions/workflows/release.yml/badge.svg" alt="release"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;PermissionRequest&lt;/strong&gt; hook for AI coding tools that delegates tool-execution permission decisions to an LLM (Claude Haiku) based on rules defined in a configuration file.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tak848/ccgate/docs/images/gate.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftak848%2Fccgate%2FHEAD%2Fdocs%2Fimages%2Fgate.png" alt="ccgate in action: a safe echo is allowed while curl ... | bash is denied with a deny_message"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Supported targets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://docs.anthropic.com/en/docs/claude-code" rel="nofollow noopener noreferrer"&gt;Claude Code&lt;/a&gt;&lt;/strong&gt; — stable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developers.openai.com/codex/hooks" rel="nofollow noopener noreferrer"&gt;OpenAI Codex CLI&lt;/a&gt;&lt;/strong&gt; — experimental&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://github.com/tak848/ccgate/docs/ja/README.md" rel="noopener noreferrer"&gt;日本語ドキュメント&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;The AI tool invokes &lt;code&gt;ccgate&lt;/code&gt; before executing a tool.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccgate&lt;/code&gt; 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.&lt;/li&gt;
&lt;li&gt;Returns Haiku's decision to the AI tool.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;CLI&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;ccgate                         Read HookInput JSON from stdin (Claude Code hook)
                               Equivalent to 'ccgate claude'. Permanent default — never
                               deprecated, so existing ~/.claude/settings.json entries
                               using&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tak848/ccgate" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What ccgate is
&lt;/h2&gt;

&lt;p&gt;Both Claude Code and Codex CLI expose a &lt;code&gt;PermissionRequest&lt;/code&gt; hook: a hook fired right before the user gets prompted for an approval. ccgate plugs into that hook.&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Typical use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pass the tool execution through&lt;/td&gt;
&lt;td&gt;Read-only inspection, project-defined tests, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deny&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reject with a message&lt;/td&gt;
&lt;td&gt;`curl \&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;{% raw %}&lt;code&gt;fallthrough&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Hand back to the user&lt;/td&gt;
&lt;td&gt;Genuinely ambiguous calls, last-mile confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;deny_message&lt;/code&gt; is delivered to the agent, so it can read "use the project script instead of &lt;code&gt;npx&lt;/code&gt;" and try again, instead of being stonewalled.&lt;/p&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Claude Code / Codex CLI
  -&amp;gt; static rules decide?
     -&amp;gt; yes: pass / block as usual
     -&amp;gt; no: PermissionRequest hook
            -&amp;gt; ccgate
            -&amp;gt; tool_input + git context + jsonnet rules
            -&amp;gt; Haiku verdict
               -&amp;gt; allow: execute
               -&amp;gt; deny: reject with reason
               -&amp;gt; fallthrough: back to user prompt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup (Claude Code)
&lt;/h2&gt;

&lt;p&gt;Install via mise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mise use &lt;span class="nt"&gt;-g&lt;/span&gt; aqua:tak848/ccgate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register the hook in &lt;code&gt;~/.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PermissionRequest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ccgate claude"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the API key for your provider. Anthropic is the default, but OpenAI and Gemini are supported too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CCGATE_ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;span class="c"&gt;# or: export CCGATE_OPENAI_API_KEY=...&lt;/span&gt;
&lt;span class="c"&gt;# or: export CCGATE_GEMINI_API_KEY=...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs on built-in defaults out of the box. To customize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ccgate claude init   &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.claude/ccgate.jsonnet
ccgate claude init &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .claude/ccgate.local.jsonnet   &lt;span class="c"&gt;# untracked, project-local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Provider config is a single block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsonnet"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'openai'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'gpt-5.4-nano-2026-03-17'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you override &lt;code&gt;provider&lt;/code&gt;, restate the whole block. That means &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;, optional &lt;code&gt;base_url&lt;/code&gt;, and optional &lt;code&gt;timeout_ms&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;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. &lt;code&gt;provider.base_url&lt;/code&gt; can point ccgate at compatible proxies. Examples include LiteLLM, Azure OpenAI, on-prem gateways, or regional endpoints.&lt;/p&gt;

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

&lt;p&gt;Register the &lt;code&gt;PermissionRequest&lt;/code&gt; hook in either &lt;code&gt;~/.codex/hooks.json&lt;/code&gt; or inline &lt;code&gt;[hooks]&lt;/code&gt; tables in &lt;code&gt;~/.codex/config.toml&lt;/code&gt;. Project-local &lt;code&gt;.codex/&lt;/code&gt; hooks load once the project is trusted. See the &lt;a href="https://developers.openai.com/codex/hooks" rel="noopener noreferrer"&gt;Codex hooks docs&lt;/a&gt; for the schema.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules in (almost) plain English
&lt;/h2&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsonnet"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;'This repository is a trusted local development repository.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'Use project-defined task runners such as make, pnpm, mise, or task when available.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;'Read-only inspection commands inside the current repository are allowed.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'Running project-defined lint, test, build, and format commands is allowed.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'Read-only GitHub CLI operations such as viewing pull requests are allowed.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="nx"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;'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.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'Do not access sibling checkouts when running inside a git worktree. deny_message: Use a path inside the current worktree.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'Do not force push or push directly to protected branches. deny_message: Destructive git operation; not auto-approved.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Why not just use Auto Mode or Codex &lt;code&gt;auto_review&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Auto Mode actually does
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://claude.com/blog/auto-mode" rel="noopener noreferrer"&gt;Auto Mode&lt;/a&gt; is a Claude Code permission mode where a separate classifier evaluates tool calls before you'd normally see a prompt. Per the &lt;a href="https://www.anthropic.com/engineering/claude-code-auto-mode" rel="noopener noreferrer"&gt;engineering write-up&lt;/a&gt; and the &lt;a href="https://code.claude.com/docs/en/permission-modes" rel="noopener noreferrer"&gt;Permission modes docs&lt;/a&gt;, it has a few specific behaviors worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read-only operations and edits inside the working directory get auto-approved without going through the classifier.&lt;/li&gt;
&lt;li&gt;Broad &lt;code&gt;Bash(*)&lt;/code&gt;-style allow rules are dropped — Auto Mode does not honor them, by design.&lt;/li&gt;
&lt;li&gt;After 3 consecutive blocks (or 20 total) the classifier hands control back to the regular permission prompt.&lt;/li&gt;
&lt;li&gt;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).&lt;/li&gt;
&lt;li&gt;You can keep Auto Mode active during Plan mode via &lt;code&gt;useAutoModeDuringPlan&lt;/code&gt; (default &lt;code&gt;true&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Codex &lt;code&gt;auto_review&lt;/code&gt; actually does
&lt;/h3&gt;

&lt;p&gt;Codex CLI doesn't have a permission mode with the same name. What it has is &lt;a href="https://developers.openai.com/codex/config-advanced#approval-policies-and-sandbox-modes" rel="noopener noreferrer"&gt;&lt;code&gt;auto_review&lt;/code&gt;&lt;/a&gt;, enabled in &lt;code&gt;config.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;approvals_reviewer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"auto_review"&lt;/span&gt;

&lt;span class="nn"&gt;[auto_review]&lt;/span&gt;
&lt;span class="py"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
... your local reviewer policy instructions ...
"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Why I still keep ccgate on
&lt;/h3&gt;

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

&lt;p&gt;The bullets I actually care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;deny_message&lt;/code&gt; flexibility.&lt;/strong&gt; A plain "no" makes an agent try a nearby bad command again. A concrete message like "use the project-defined script instead of &lt;code&gt;npx&lt;/code&gt;" gives it a path to recover. I want to keep authoring that text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same shape across both CLIs.&lt;/strong&gt; I use Claude Code and Codex CLI both. With Auto Mode + &lt;code&gt;auto_review&lt;/code&gt;, 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 &lt;code&gt;libsonnet&lt;/code&gt; file imported from each side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project-local layering.&lt;/strong&gt; Per-repo rules layer on top of my personal defaults via &lt;code&gt;append_allow&lt;/code&gt; / &lt;code&gt;append_deny&lt;/code&gt; / &lt;code&gt;append_environment&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything readable on disk.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit &lt;code&gt;fallthrough&lt;/code&gt;.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan mode coverage as a side effect.&lt;/strong&gt; 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 &lt;code&gt;rg&lt;/code&gt; / &lt;code&gt;find&lt;/code&gt; / &lt;code&gt;git&lt;/code&gt; through &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;$()&lt;/code&gt;, that's a real difference in feel.&lt;/li&gt;
&lt;/ul&gt;

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

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

&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;p&gt;In my environment, between 2026-04-01 and 2026-04-28, ccgate handled 2,079 PermissionRequest-class events for Claude Code:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;2,079&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allow&lt;/td&gt;
&lt;td&gt;1,764&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deny&lt;/td&gt;
&lt;td&gt;190&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fallthrough&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automation rate&lt;/td&gt;
&lt;td&gt;94.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ccgate is not a security boundary. Hard limits (&lt;code&gt;permissions.deny&lt;/code&gt;, sandboxing, managed settings) should still do that job.&lt;/li&gt;
&lt;li&gt;It costs API calls. Manageable in interactive use but worth watching for high-volume unattended runs.&lt;/li&gt;
&lt;li&gt;Plan-mode "correctness" depends on prompts. Not a hard guarantee. Tracked as &lt;a href="https://github.com/tak848/ccgate/issues/37" rel="noopener noreferrer"&gt;Issue #37&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Auto Mode, Claude Code hooks, and Codex hooks are evolving fast. Always check the live docs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;If permission prompts have you tired but &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; feels like jumping off a cliff, this might be the in-between you want.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/tak848/ccgate" rel="noopener noreferrer"&gt;https://github.com/tak848/ccgate&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback and PRs welcome.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>opensource</category>
      <category>claude</category>
    </item>
  </channel>
</rss>
