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.mjs outcome · before the call leaves the harness pretooluse · the model proposes a tool call 1 · known to catalog.json? the committed catalog is the source of truth — not the server no deny not an operation of this bundle tamper / staleness guard — a swapped or stale server is refused known tool 2 · state === approved? the server never compiles an unapproved operation into a tool no deny draft / rejected — not approved for use mirrors the approval filter — unapproved ops never ship approved 3 · confirmation required · human-approval tier? AIR confirmation.humanApproval — the strictest tier yes ask escalate to the human permission dialog Antigravity: force_ask — always prompts, ignores "Always Allow" the model cannot self-approve no human tier 4 · confirmation required · confirm !== true? the model must state the intended effect inside the call yes deny re-invoke with confirm: true preview with dryRun: true first — fixed in the same turn confirm: true · or exempt 5 · idempotency key missing where required? idempotency "required" and no idempotency_key in the input yes deny supply idempotency_key reusing a key is safe; a new key is a new operation key present · or optional 6 · high-risk or irreversible mutation? the call is clean — steer it, don't block it yes allow proceed — with dry-run steering context injected: a dryRun preview is available read · low-risk · reversible allow reads get zero noise on the hot path every 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.

Harnessask becomesReason
Antigravityforce_askForces a user prompt and ignores cached Always Allow state
Claude CodeaskUses the harness permission dialog
CodexdenyPreToolUse 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.