agent-gatekeeper (gatekeeper-claude)
A fast PreToolUse permission hook for coding agents — Claude Code, OpenAI Codex CLI, and xAI grok — that replaces glob-based permission arrays with PCRE2-compatible regex rules. One policy, enforced across every harness.
Naming (2026-07-10): the public GitHub repo is
jim80net/gatekeeper-claude
(renamed from claude-gatekeeper; old URLs 301 to the new name).
The binary, Claude Code plugin id, and Go module path still use
claude-gatekeeper so fleet dogfood and existing installs keep working.
Full matrix: COMPAT.md. Product family (core + adapters):
gatekeeper-flotilla charter.
Why
Agent permission globs (Bash(git add:*)) can't match env-prefixed commands like FOO=bar git commit, pipe chains, HEAD:main refspecs, or complex argument patterns. Regex can.
agent-gatekeeper evaluates every tool call against a layered set of regex rules and returns allow, deny, or abstain (no opinion — the harness's native permission system decides). Deny always wins. When a tool call is denied, the agent sees the reason and can adjust.
Harnesses
One binary serves three harnesses, selected by --harness (or the GATEKEEPER_HARNESS env var; defaults to claude). Tool names are normalised to a canonical taxonomy inside each adapter, so the same gatekeeper.toml rules apply verbatim to all three.
| Harness | Wire | Status |
|---|
claude | hookSpecificOutput.permissionDecision (exit 0) | Stable. Byte-for-byte compatible with prior releases. |
codex | Claude-compatible hookSpecificOutput envelope, narrower value set | Live-verified (2026-07-03, codex-cli 0.142.5); ALLOW-value fix 2026-07-08. permissionDecision:"deny" blocks even under approval_policy=never (full auto); silent abstain falls through to the native policy; codex reads both ~/.codex/hooks.json (global) and project .codex/hooks.json. |
grok | grok-native {"decision":"deny","reason":...} (exit 2 deny / exit 0 allow); abstain = fail-open (exit 1) | Live-verified (2026-07-03, grok 0.2.82). A global ~/.grok/hooks/ PreToolUse hook deny ({"decision":"deny"} + exit 2) blocks the tool call even under --permission-mode bypassPermissions (full auto); an abstaining call (exit 1, no output) falls through and runs. |
codex is verified as a real gate: on a fully-autonomous codex agent (approval_policy=never), a hook deny — and therefore on_error = "deny" — actually blocks the command. The default abstain falls through to the native policy exactly as documented. Only deny is ever emitted explicitly. permissionDecision:"allow" and permissionDecision:"ask" are both rejected by codex's own PreToolUse handler ("PreToolUse hook returned unsupported permissionDecision:allow"/"...ask" — extracted verbatim from the codex-cli 0.143.0 binary, despite both being schema-legal members of the shared wire enum) — an ALLOW verdict is therefore encoded identically to abstain (nothing written), never as an explicit "allow".
grok is verified as a real gate too (2026-07-03 live probe, grok 0.2.82). In an isolated sandbox under --permission-mode bypassPermissions (grok's full-auto), a global ~/.grok/hooks/ PreToolUse hook that emits the gatekeeper's grok-native {"decision":"deny"} + exit 2 blocked a canary command (the file was never created), while an abstaining call (exit 1) let a control command run. So the gatekeeper's grok adapter is a real in-harness hard control on auto-approve grok agents — grok evaluates PreToolUse hooks before the permission system, "regardless of permission mode." Re-confirmed under the exact deploy config permission_mode = "always-approve" (which grok resolves to the same bypassPermissions mode it reports in the hook payload): deny blocked, abstain ran.
Grok 0.2.101's shipped hook guide and locally captured shipped tool schemas also pin these native input shapes: run_terminal_command.command, read_file.target_file, search_replace.file_path, write.file_path, list_dir.target_directory, grep.pattern, and web_fetch.url. Golden hook fixtures live in internal/adapter/grok/testdata. web_search is a verified native tool name, but its primary input field remains unverified without a live hook capture; the adapter deliberately does not guess one, so input-targeted WebSearch rules are not supported yet.