# Antigravity Hooks Reference — force_ask, Fail-Closed, and What --dangerously-skip-permissions Actually Removes

Google Antigravity's five hook events, the decision vocabulary with a native force_ask, why a crashed hook blocks the call, and why --local isn't wired yet.

<div style="margin:8px 0 28px;padding:20px 22px;border:1px solid var(--line-2);border-radius:12px;background:var(--color-accent-light,#f0effe);">
  <p style="margin:0 0 12px;font-size:15px;line-height:1.6;color:var(--acp-text);"><strong>Just want the answer?</strong> An Antigravity hook is a command registered in <code>hooks.json</code> that answers <code>PreToolUse</code> calls with a JSON decision: <code>allow</code>, <code>deny</code>, <code>ask</code>, <code>force_ask</code>, or <code>deny_unless_prior_grant</code>. A crashed hook blocks the call — fail-closed by default. One entry in <code>~/.gemini/config/hooks.json</code>:</p>
  <pre data-track="AntigravityHooks: Hero Config Copy" style="margin:0 0 12px;background:var(--color-surface,#faf9ff);border:1px solid var(--line-2);border-radius:8px;padding:12px 14px;overflow-x:auto;"><code>{
  "hooks": {
    "PreToolUse": [
      { "matcher": "*",
        "hooks": [ { "type": "command", "command": "node ~/.acp/hooks/antigravity/hook.mjs" } ] }
    ]
  }
}</code></pre>
  <p style="margin:0;font-size:13px;color:var(--acp-text-dim);"><a href="/integrations/antigravity" data-track="AntigravityHooks: Setup Guide" style="font-weight:600;">Install the ACP hook in one command &rarr;</a> &nbsp;&middot;&nbsp; <a href="/controls/antigravity" data-track="AntigravityHooks: Controls Page">Antigravity's full control model &rarr;</a> &nbsp;&middot;&nbsp; free up to 5 agents</p>
</div>

Antigravity is Google's agent-first development ecosystem — the IDE, the `agy` CLI that replaced Gemini CLI in May 2026, and extensions for VS Code, JetBrains, Zed, and Xcode, all reading one shared hook registration. Its hook system is younger than Claude Code's and narrower than Cursor's, but it makes two choices neither of those harnesses makes: a hook's `ask` is a first-class decision the human actually sees, and a hook that fails blocks the call rather than letting it through. This is the reference for the five events, the decision vocabulary, and the one flag that erases all of it.

<!-- source: https://agenticcontrolplane.com/controls/antigravity -->

## Where hooks live

`hooks.json` reads from two locations, shared across every Antigravity surface:

| Scope | Location | Notes |
|---|---|---|
| Workspace | `.agents/hooks.json` | Behind folder trust — trust the folder and its hooks are live |
| User (global) | `~/.gemini/config/hooks.json` | Shared by the CLI, the IDE, and the app — where the ACP installer writes, merged under its own key |

<!-- source: https://agenticcontrolplane.com/controls/antigravity -->

## The five events

`PreToolUse`, `PostToolUse`, `PreInvocation`, `PostInvocation`, `Stop`. Tool-scoped events take a regex `matcher` against the tool name — `run_command|view_file`, `browser_.*`, or `*` to catch everything. Handlers are shell commands (`type: "command"` is the only kind), a 30-second default timeout, camelCase JSON on stdin, a JSON decision on stdout.

`PreInvocation` and `PostInvocation` can inject steps into the agent loop, and `PostInvocation` can force-continue or terminate it. `Stop` hooks can re-enter the loop with `decision: "continue"` — capped at a built-in limit of consecutive continuations, so an always-blocking stop hook can't hold the agent hostage indefinitely.

One contract quirk worth knowing before you write a handler: **the payload doesn't name its own event.** Unlike every Claude-lineage hook, there's no `hookEventName` field on the stdin JSON — a handler serving more than one event has to be registered with the event passed as an argument, not read from the payload.

<!-- source: https://agenticcontrolplane.com/controls/antigravity -->

## The decision vocabulary, and the first native `ask`

A `PreToolUse` hook answers:

```json
{ "decision": "deny", "reason": "…" }
```

where `decision` is one of **`allow`, `deny`, `ask`, `force_ask`, `deny_unless_prior_grant`**, with an optional `permissionOverrides` array that can grant `action(target)` resources for the rest of the turn. `ask` prompts the human but respects a cached "Always Allow" grant from earlier in the session; **`force_ask` prompts unconditionally, ignoring that cache.**

That distinction is the headline. No other mainstream harness lets a hook hand a call to the human as a first-class outcome the way `force_ask` does — Claude Code's hooks can return `ask`, but a locally cached allow can still shortcut it in some flows; most other harnesses' hook gates are deny-only.

<!-- source: https://agenticcontrolplane.com/controls/antigravity -->

## `PostToolUse`: audit, not content scanning

`PostToolUse` fires after the tool call completes, with `toolCall` and an `error` string in the payload — **not the tool's actual output.** That's a hard limit worth knowing up front: a `PostToolUse` hook can log that a call happened and whether it failed, but it cannot inspect what a `Read` returned or scan a shell command's stdout for secrets from the hook payload alone. The full exchange lives in the transcript file the payload points at, if you need to go further.

## Two sharp edges beyond the vocabulary

**The hook environment is sanitized.** Only whitelisted variables reach the subshell a hook runs in, so env-var-based configuration of a hook mostly doesn't work — configuration has to ride in a file the hook reads itself, not the process environment.

**A Git repo's `.git` directory mounts read-only** inside the sandbox, even for an otherwise-writable agent, when `enableTerminalSandbox` is on.

<!-- source: https://agenticcontrolplane.com/controls/antigravity -->

## Fail-closed: the core

Every event above shares one behavior, and it's the opposite of Claude Code's and Cursor's default: **a failing `PreToolUse` hook blocks the call.** Crash, timeout, non-zero exit — Antigravity reports "pre-tool hook failed" and the tool call does not run. There's no exit-code deny channel either; a denial has to travel as JSON with exit 0.

That cuts both ways. No silent lapse of a broken policy hook — good, and the opposite of a fail-open core where an unreachable backend quietly turns enforcement off. But a hook whose own upstream dependency goes down can brick every tool call in the session unless the hook itself carries a fallback posture. [The ACP hook for Antigravity](/integrations/antigravity) answers this directly: it always exits 0 with a JSON decision, including its own crash handling, and applies its own two-tier posture on top of Antigravity's fail-closed core — attended sessions fail open with a loud `[ACP] UNGOVERNED` warning so a gateway outage never bricks an interactive session; unattended tiers fail closed, because nobody is there to notice a silent gap.

<!-- source: https://agenticcontrolplane.com/controls/antigravity -->

## The escape hatch removes the surface itself

`--dangerously-skip-permissions` auto-approves every tool permission request in Antigravity — and verified live on `agy` 1.1.21, with an A/B pair of otherwise-identical headless turns, it does something no other mainstream harness's bypass flag does: **`PreToolUse` hooks are not invoked at all under the flag.** No hook execution, no decision, no record.

That's the opposite of [Claude Code, where deny rules and hooks still fire under `--dangerously-skip-permissions`](/blog/claude-code-hooks-reference) and a hook `deny` still blocks the call. Treat the flag's appearance as an alertable event in any fleet, and use scoped `permissions.allow` rules instead of the flag to unblock headless runs.

<!-- source: https://agenticcontrolplane.com/controls/antigravity ; https://github.com/agentic-control-plane/antigravity-acp-plugin -->

## What the ACP hook does with the same contract

The installer detects Antigravity (the `agy` CLI, or an existing `~/.gemini` profile), places the hook at `~/.acp/hooks/antigravity/hook.mjs`, and merges its registration into `~/.gemini/config/hooks.json` under its own `acp` key — the file is shared across the CLI, IDE, and app, so the merge never overwrites an existing hook already registered there. Before any tool runs, the hook is consulted via `/govern/tool-use`; after it runs, a completion record goes to `/govern/tool-output`; on session end, a receipt with a console review link lands in the scrollback.

Two mapping details worth knowing: Antigravity's native tool names (`run_command`, `view_file`, `replace_file_content`, and so on) are normalized to a canonical shape before the policy check runs, with the native name preserved in the audit record as `client_tool_name` — verified live, `run_command`'s argument key is `CommandLine`, and the production hardline floor correctly denied a recursive root delete issued under that native name. And an ACP `ask` decision maps to `force_ask`, so a policy hold renders as a native Antigravity approval card rather than a generic block.

## Known limitations

- **`PostToolUse` carries no tool output.** Content scanning of what a call actually returned isn't possible from the hook payload; only the transcript file has it.
- **`--dangerously-skip-permissions` is total.** No hook — this one included — is consulted under the flag. There's no deny that survives it.
- **`--local` isn't wired for Antigravity today.** ACP's on-device decision engine (`~/.acp/policy.json`, decisions logged to `~/.acp/audit.jsonl`, no account) ships for Claude Code and Cursor; Antigravity's hook currently reaches the hosted gateway for every decision. A workspace connection is required.
- **The hook environment is sanitized**, so configuration has to live in a file (`~/.acp/config.json`, snake_case keys) rather than environment variables — unattended fleets should pin `"agent_tier": "background"` there explicitly.
- **Hooks see tool calls, not tokens.** Spend lives on the model path, not the hook payload — see [Turn on Cost X-Ray](/cost-tracking) to meter Antigravity's model calls separately.

## Comparing to Claude Code and Cursor hooks

Claude Code and Cursor both fail open by default when a hook breaks; Antigravity fails closed. Claude Code's bypass flag and Cursor's Run Everything mode both leave hooks running; Antigravity's bypass flag removes them. And where Claude Code's `ask` and Cursor's `preToolUse` `ask` are either soft or unenforced, Antigravity's `force_ask` is a decision the human is guaranteed to see. Three different harnesses, three different answers to what a hook is allowed to fail into — [the Claude Code hooks reference](/blog/claude-code-hooks-reference) and [the Cursor hooks reference](/blog/cursor-hooks-reference) have the other two contracts in full.

## Frequently asked questions



## Where to read more

- [Antigravity's full control model, explained](/controls/antigravity) &mdash; the permissions engine, the sandbox, headless posture, and the enterprise plane
- [Install ACP for Antigravity](/integrations/antigravity) &mdash; the hook, the merge behavior, and verified-vs-pending status
- [antigravity-acp-plugin on GitHub](https://github.com/agentic-control-plane/antigravity-acp-plugin) &mdash; MIT, zero dependencies, one readable file
- [Claude Code hooks reference](/blog/claude-code-hooks-reference) &mdash; the closest sibling contract, fail-open instead of fail-closed
- [Cursor hooks reference](/blog/cursor-hooks-reference) &mdash; a third fail-open core, with an LLM classifier ahead of it
- [Codex CLI hooks reference](/blog/codex-cli-hooks-reference) &mdash; a narrower, shell-first hook surface
