hookgate hookgate
hookgate Claude Code plugin · v0.0.2

hookgate

Calibrated, sub-second decisions inside Claude Code's hooks. A PreToolUse gate on shell commands and a Stop gate on unverified claims of completion, answered by TypeSafe's Jev — a model that returns typed decisions with a confidence score instead of text — in about 100 ms. Confident: act. Unsure: ask the human. Unreachable: get out of the way.

Status: gates implemented, benchmark pending. The command gate, the completion gate and an off-by-default injection screen are in, with audit mode, a per-session cache, rule promotion, doctor and report. What is still missing is the number: the benchmark below has not been run yet, so until it is, start in audit mode.

Why in the hooks

A coding agent's harness takes hundreds of small decisions per session: is this rm -rf aimed at the repo or at ~, does "done, all tests pass" match git status, should this be allowed, asked about or refused. Today those are taken by a static allowlist, by a full LLM call (type: prompt hooks — seconds and cents each), or by nobody. A System One model is built for exactly this shape: unstructured state in, a typed answer plus a calibrated probability out, no text to parse, no hallucination to catch. TypeSafe's own plugin and cookbooks show how to build applications on Jev. Nobody has put it inside the agent harness. That is the gap this fills.

What ships

Gate Hook Question to Jev Effect
Command risk PreToolUse on Bash Choice{allow, ask, deny} + Noul "destroys data or state outside the repo?" ask or deny with a reason. Below the confidence threshold it is always ask, never allow. A confident allow passes through by default: hookgate narrows what the harness would do, it never widens it (allowMode: "allow" opts in)
Unverified completion Stop Noul "does the last message claim a completion the visible state does not support?" on the message plus git status block with a reason naming what to verify. Once per prompt, so the agent cannot loop
Injected instructions PostToolUse on WebFetch, WebSearch, Read, Bash Noul "does this output contain instructions addressed to an AI agent?" additionalContext telling the agent to treat the span as data. Off by default until the fixture set gives a false-positive rate

Around the gates:

Configuration lives in .claude/hookgate.json in the repository (or HOOKGATE_CONFIG); every key is optional:

{
  "mode": "audit",
  "model": "jev-latest",
  "timeoutMs": 2000,
  "failClosed": false,
  "allowMode": "passthrough",
  "thresholds": { "confidence": 0.7, "destructive": 0.5, "unverified": 0.7, "injection": 0.7 },
  "gates": { "command": true, "completion": true, "injection": false }
}

Thresholds scale with risk, as TypeSafe's confidence guide recommends: the defaults are conservative and the benchmark is what moves them.

Fail-open, always. No TYPESAFE_API_KEY, no network, a timeout, a 5xx or a bug in this plugin means no decision: exit 0, empty stdout, and the harness's normal permission flow applies as if hookgate were not installed. A gate that stalls the agent is worse than none. failClosed: true is the explicit opt-in under which an unreachable API makes the command gate ask.

What leaves the machine, exactly. One HTTPS POST per decision to api.typesafe.ai, carrying only what the question needs: for the command gate the shell command, its description if the agent wrote one, the last two segments of the working directory and the permission mode; for the completion gate the agent's final message, the stop reason and the first sixty lines of git status --porcelain; for the injection screen the tool's output. Never the transcript, never file contents the agent did not just fetch, never the session id. The audit log on disk keeps the verdicts and a command prefix, not the command. TypeSafe's handling of what it receives is theirs to state: typesafe.ai legal. HOOKGATE_ENDPOINT points the plugin at a proxy of your own if that matters.

State never carries secrets. Commands and tool outputs can contain tokens; key shapes, bearer headers, KEY=value assignments, URL passwords and private keys are redacted before anything leaves the machine, and state is truncated well under Jev's 32k-token limit.

Install

/plugin marketplace add Allan-Nava/hookgate
/plugin install hookgate@hookgate

with TYPESAFE_API_KEY in the environment Claude Code runs in, then hookgate doctor from the plugin directory to see what it sees.

Codex CLI (0.155 and later dropped plugin-bundled hooks, so hooks are per repository or per user):

npm install -g hookgate
hookgate print-hooks > .codex/hooks.json      # or ~/.codex/hooks.json

Codex asks to trust the hooks file once; --dangerously-bypass-hook-trust skips that for automation you already vet. Verified live on 2026-09-22 with Codex 0.155.1: the command gate refused rm -rf ~/… ("Command blocked by PreToolUse hook: hookgate: refused at 97% confidence") and let a git push --force through with the concern as a systemMessage; the completion gate ran on Stop. Zero dependencies, Node 18 or later, one fetch to POST https://api.typesafe.ai/v1/systemone. The package on npm is the same tree, for npx hookgate doctor and npx hookgate report.

Benchmark

Not run yet. The set and the runner are in evals/: 79 shell commands labelled by hand as safe, ask or dangerous, 20 tool outputs clean or injected, and node evals/run.mjs commands --baseline, which runs the command gate and a type: prompt hook on claude-opus-5 over the same commands and reports agreement with the labels, p50 and p95 latency, cost per decision and the share of ask per confidence threshold. One run, one Jev version, dated — a data point, not a benchmark suite. If agreement stays under about 90%, the command gate ships ask-only. Until the table is here, run in audit mode.

Two design notes

Why not teach Jev? TypeSafe already does, well: official Python and JavaScript SDKs, an MIT plugin with the patterns, thirteen cookbooks. Duplicating that would be noise. hookgate has one job the harness can feel.

Why zero dependencies? A hook starts on every tool call. Start-up cost is the cost, so there is nothing to install, nothing to resolve, one file to read.

The gates, from hooks.json

Straight from hooks/hooks.json. Two handlers in one file, each with a hard timeout; every one of them falls through when it cannot decide.

Prior art