Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Antigravity CLI Hooks Reference

Disclaimer: This document reflects our current understanding of Antigravity CLI’s hook system. It is a working reference for symposium development, not a substitute for the official docs. Details may be outdated or incomplete — always consult the primary sources.

Primary sources: Hooks · Plugins & Skills · GitHub repo · the bundled agy-customizations skill under ~/.gemini/antigravity-cli/builtin/skills/, which is more precise than the website

Google’s Antigravity CLI (agy) exposes shell-command hooks through a dedicated hooks.json. Antigravity ships three surfaces — the CLI, the IDE, and the web app — which share the same configuration roots.

Configuration

FileScope
~/.gemini/config/hooks.jsonUser-global
<repo>/.agents/hooks.jsonProject-scoped

Both are additive; all matching hooks run, each with its working directory set to the directory containing its own hooks.json.

Project scope works in ordinary interactive use: agy walks up from the working directory to find .agents/, and loads project hooks on a second pass once it has adopted the workspace.

Headless print mode is the exception. agy -p adopts no workspace unless given --add-dir <absolute path> — it cannot read project files and loads no project .agents/ configuration. A relative --add-dir . is ignored. Automation driving agy -p against a project must pass an absolute path.

Configuration structure

Each top-level key is a hook name mapping to its events. PreToolUse and PostToolUse wrap handlers in a matcher group; PreInvocation, PostInvocation and Stop take flat handler lists.

{
  "symposium": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          { "type": "command", "command": "cargo-agents hook antigravity pre-tool-use", "timeout": 30 }
        ]
      }
    ],
    "PreInvocation": [
      { "type": "command", "command": "cargo-agents hook antigravity user-prompt-submit", "timeout": 30 }
    ]
  }
}

enabled: false on a named hook disables all its handlers. timeout is in seconds and defaults to 30. Only type: "command" is supported; commands run via sh -c (cmd /c on Windows).

Events

EventWhen it firesMatcher
PreToolUsebefore a tool step executestool name
PostToolUseafter a tool step completestool name
PreInvocationbefore the model is calledn/a
PostInvocationafter each model invocation completesn/a
Stopwhen the execution loop terminatesn/a
SessionStartwith the first turn of a new conversationn/a

SessionStart is absent from the official documentation but present in the binary’s hook proto (the HookArgs oneof has exactly six variants: PreTool, PostTool, PreInvocation, PostInvocation, SessionStart, Stop), and it works: registered in hooks.json it loads and carries a populated workspacePaths. It fires with the first turn of a new conversation, in the TUI at the first prompt rather than at launch. Resuming a conversation (-c, --conversation, or the TUI) starts a new process that never fires it, so a resumed conversation skips symposium’s SessionStart refresh until a new conversation starts. PreInvocation by contrast fires before every model call, and its invocationNum restarts at 0 each turn, so it marks the start of a turn rather than of a session. That is why symposium maps it to user-prompt-submit and dispatches only invocationNum == 0 (AgentHookInput::should_dispatch). Verified on agy 1.2.7.

Unknown event keys are accepted silently and never fire, so a misspelled event name fails with no error at all.

Matchers are regexes over tool names, which are the lowercased step type without its CORTEX_STEP_TYPE_ prefix — run_command, view_file, browser_.*. A matcher written for another agent’s tool names (Bash, Edit) will not match.

Input Schema (stdin)

All keys are camelCase (protojson).

Base fields (all events)

{
  "conversationId": "5e4f131c-…",
  "workspacePaths": ["/path/to/workspace"],
  "transcriptPath": "…/transcript_full.jsonl",
  "artifactDirectoryPath": "…",
  "modelName": "gemini-3.7-flash-high"
}

workspacePaths is populated once the workspace is adopted, and empty under agy -p without --add-dir. A hook’s working directory is always the directory holding its own hooks.json, never the user’s project.

PreToolUse / PostToolUse additions

{
  "toolCall": { "name": "run_command", "args": { "CommandLine": "npm test" } },
  "stepIdx": 2,
  "error": ""
}

error is present on PostToolUse only. Both events carry toolCall.

PreInvocation / PostInvocation additions

{ "invocationNum": 0, "initialNumSteps": 1 }

Stop additions

{ "executionNum": 0, "terminationReason": "NO_TOOL_CALL", "error": "", "fullyIdle": true }

Output Schema (stdout)

PreToolUse

{ "decision": "allow", "reason": "optional", "permissionOverrides": [] }

decision is required: allow, deny, ask, force_ask, or deny_unless_prior_grant. An overwrite object shallow-merges into the tool call’s arguments before it runs.

Writing {} denies the call. So do {"decision": ""} and objects carrying only other fields. Writing nothing at all allows. Any hook that does not intend to block must emit an explicit {"decision": "allow"}.

PostToolUse

Expects {}.

PreInvocation — inject context

{ "injectSteps": [{ "ephemeralMessage": "..." }] }

Each step accepts one of toolCall, userMessage, or ephemeralMessage. This is the equivalent of Claude Code’s additionalContext.

PostInvocation

injectSteps as above, plus terminationBehavior: force_continue, terminate, or omitted.

Stop

{ "decision": "continue", "reason": "required when continuing" }

Any value other than continue lets the agent stop.

Exit Codes

Ignored. Exit 1 and exit 2 behave exactly as exit 0; only stdout decides the outcome. This differs from every other agent symposium supports, where exit 2 blocks.

Skills

ScopePath
Project<repo>/.agents/skills/<name>/SKILL.md
Global~/.gemini/config/skills/<name>/SKILL.md

A skill is a directory containing SKILL.md with name and description frontmatter. Additional files and subdirectories (scripts/, examples/, resources/, references/) are supported, so symposium’s .symposium marker and .gitignore are preserved. The CLI also reads ~/.gemini/antigravity-cli/skills/ and ~/.gemini/skills/, but ~/.gemini/config/skills/ is the location all three surfaces recognize.

Registering skills from another location

~/.gemini/config/skills.json registers skill directories stored outside the default locations, and is read on every run:

{ "entries": [{ "path": "/abs/path/to/repo/.agents/skills", "exclude": ["experimental-.*"] }] }

Paths must be absolute. The schema also documents workspace-relative paths, but an entry of .agents/skills resolves to nothing in practice.

The list is global with no notion of the active repository, so every indexed directory loads in every session; include_only and exclude filter by skill directory name, not by workspace. Symposium does not use this file — project skills are found by ordinary discovery — but it is the mechanism for skills kept outside the standard locations.

Symlinks are followed, both a symlinked skill directory inside a skills folder and a symlink of the folder itself. A separate report of symlinked skills being ignored concerns the IDE and ~/.gemini/antigravity/skills/.

plugins.json follows the same schema for plugin directories.

MCP server configuration

ScopePath
Project<repo>/.agents/mcp_config.json
Global~/.gemini/config/mcp_config.json

Standard mcpServers object; stdio entries use command/args/env, remote entries use serverUrl. Unlike Gemini CLI, MCP configuration does not share a file with hooks. agy mcp add has no scope flag and writes the global file.

Custom instructions

GEMINI.md and AGENTS.md at the workspace root, plus .agents/rules/*.md, loaded by walking up to the repository root.