Skip to content

How the hook decides

Generated hooks check a tool call before it leaves the coding harness.

Each bundle contains one decision core: plugin/hookcore.mjs.

It reads catalog.json and returns one of three results:

  • allow: continue;
  • deny: block the call; or
  • ask: require human approval.

Harness-specific shims translate that result into the format expected by Antigravity, Claude Code, or Codex.

decision core · plugin/hookcore.mjsoutcome · before the call leaves the harnesspretooluse · the model proposes a tool call1 · known to catalog.json?the committed catalog is the source of truth — not the servernodenynot an operation of this bundletamper / staleness guard — a swapped or stale server is refusedknown tool2 · state === approved?the server never compiles an unapproved operation into a toolnodenydraft / rejected — not approved for usemirrors the approval filter — unapproved ops never shipapproved3 · confirmation required · human-approval tier?AIR confirmation.humanApproval — the strictest tieryesaskescalate to the human permission dialogAntigravity: force_ask — always prompts, ignores "Always Allow"the model cannot self-approveno human tier4 · confirmation required · confirm !== true?the model must state the intended effect inside the callyesdenyre-invoke with confirm: truepreview with dryRun: true first — fixed in the same turnconfirm: true · or exempt5 · idempotency key missing where required?idempotency "required" and no idempotency_key in the inputyesdenysupply idempotency_keyreusing a key is safe; a new key is a new operationkey present · or optional6 · high-risk or irreversible mutation?the call is clean — steer it, don't block ityesallowproceed — with dry-run steeringcontext injected: a dryRun preview is availableread · low-risk · reversibleallowreads get zero noise on the hot pathevery rule mirrors an executor refusal — the generated conformance test enforces hook ↔ executor agreement.
fig 06 · the hook decides in the executor's order — deny, ask, or allow before the model burns a turn

The hook applies the same checks as the runtime executor, in the same order:

  1. unknown operation;
  2. approval state;
  3. confirmation; and
  4. idempotency.

The hook improves feedback time. It is not the trust boundary.

Remove the hook and runtime policy still applies.

This matters in two cases:

  • Missing confirmation or idempotency is rejected inside the harness. The model can repair the call without a failed network path.
  • Human approval remains human approval. A model-supplied argument cannot satisfy that gate.

tests/conformance.test.ts checks that hookcore.decide() and the runtime executor agree. If the hook allows a call the executor would refuse, conformance fails.

allow and deny map directly in all supported harnesses. ask differs because not every harness exposes the same approval primitive.

Harness ask becomes Reason
Antigravity force_ask Forces a user prompt and ignores cached Always Allow state
Claude Code ask Uses the harness permission dialog
Codex deny PreToolUse has no interactive ask, so the hook blocks the call and leaves approval to the harness flow

Antigravity runs hooks for built-in tools as well as Anvil operations. Its shim passes through tools that are not in this bundle’s catalog. The deny-unknown rule applies only to operations owned by the bundle.