Antigravity Hooks Reference — force_ask, Fail-Closed, and What --dangerously-skip-permissions Actually Removes
Just want the answer? An Antigravity hook is a command registered in hooks.json that answers PreToolUse calls with a JSON decision: allow, deny, ask, force_ask, or deny_unless_prior_grant. A crashed hook blocks the call — fail-closed by default. One entry in ~/.gemini/config/hooks.json:
{
"hooks": {
"PreToolUse": [
{ "matcher": "*",
"hooks": [ { "type": "command", "command": "node ~/.acp/hooks/antigravity/hook.mjs" } ] }
]
}
}
Install the ACP hook in one command → · Antigravity's full control model → · free up to 5 agents
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.
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 |
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.
The decision vocabulary, and the first native ask
A PreToolUse hook answers:
{ "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.
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.
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 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.
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 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.
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
PostToolUsecarries 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-permissionsis total. No hook — this one included — is consulted under the flag. There’s no deny that survives it.--localisn’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 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 and the Cursor hooks reference have the other two contracts in full.
Frequently asked questions
What hook events does Google Antigravity support?
Five: PreToolUse, PostToolUse, PreInvocation, PostInvocation, and Stop. They register in hooks.json — workspace-local at .agents/hooks.json (behind folder trust) or user-global at ~/.gemini/config/hooks.json, shared by the CLI, the IDE, and the app. Tool events take a regex matcher on the tool name; a matcher of ‘*’ catches every tool call.
What can an Antigravity hook decide?
A PreToolUse hook answers with decision: allow, deny, ask, force_ask, or deny_unless_prior_grant, plus an optional permissionOverrides array. ask respects a cached Always-Allow grant; force_ask ignores it and prompts unconditionally. No other mainstream harness’s hook vocabulary includes a native ask as a first-class outcome the way force_ask does.
What happens when an Antigravity hook crashes or times out?
The tool call is blocked. Antigravity treats a failing PreToolUse hook — crash, timeout, non-zero exit — as an error and fails closed: the opposite of Claude Code’s and Cursor’s fail-open defaults. A hook you install must always exit 0 with a JSON decision and carry its own posture for what happens when its own upstream is unreachable.
Does --dangerously-skip-permissions disable Antigravity hooks?
Yes, completely — verified live on agy 1.1.21. Under the flag, PreToolUse hooks are not invoked at all; there’s no hook execution, no decision, and no record. That’s different from Claude Code, where hooks still fire and a deny still blocks under its bypass flag. On Antigravity the flag removes the interception surface itself, not just the prompts.
Where to read more
- Antigravity’s full control model, explained — the permissions engine, the sandbox, headless posture, and the enterprise plane
- Install ACP for Antigravity — the hook, the merge behavior, and verified-vs-pending status
- antigravity-acp-plugin on GitHub — MIT, zero dependencies, one readable file
- Claude Code hooks reference — the closest sibling contract, fail-open instead of fail-closed
- Cursor hooks reference — a third fail-open core, with an LLM classifier ahead of it
- Codex CLI hooks reference — a narrower, shell-first hook surface