antigravity-hooks-valid¶
hooks.json must use Antigravity's hook events, handler types and fields
| Severity | error (auto) |
| Autofix | - |
| Since | v0.20.0 |
| Repo Types | antigravity, antigravity-plugin |
| Category | Google Antigravity |
Why¶
A customization root — .agents/, .agent/, _agents/ or _agent/ — can
carry a hooks.json that runs shell commands and injects prompts around
Antigravity's lifecycle events: before and after a tool runs, before and
after an invocation, when a session starts, and when the agent stops. The
file is committed, so the hooks are the team's, not one developer's. A
plugin under plugins/<name>/ carries its own hooks.json alongside its
manifest.
Antigravity tells nobody when it refuses one. Measured against agy 1.1.26:
every load-time rejection drops the whole file, logs a single
failed to parse hooks.json at <path> line to the debug log, and exits 0 —
so a sibling hook that was working stops running and CI stays green. A key
the parser does not recognise is quieter still: it is discarded, the file
loads, and the hook it configures simply never fires.
This rule reports both, and says which is which. Command execution is also
scanned for security by hooks-dangerous and can be
inventoried against an allowlist with
hooks-prohibited.
Severity¶
Errors — Antigravity loads no hook from the file:
- Invalid JSON, a non-finite number (
NaN,Infinity,-Infinity), a trailing comma or a comment. The parser is strict JSON. - A UTF-8 byte-order mark (BOM). Remove it; the loader does not strip it.
- A non-null root that is not an object of named hooks.
- A named hook that is not an object. An
enabledkey at the top level is reported this way too, with its own wording: every top-level key is a hook name, so there is no file-level switch to write there. - An
enabledinside a named hook that is not a boolean. - An event whose value is not an array; a group or a handler that is not an
object; a
matcherthat is not a string; a group'shooksthat is not an array. - A handler
typeoutsidecommandandprompt. The comparison is case-sensitive:"COMMAND"is refused. - A command hook carrying
promptormodel, or a prompt hook carryingcommand. - A
timeoutthat is not a whole number, or one outside-2147483648to2147483647. It is a 32-bit integer:0and negative values load; a float, a string, or a number past either end does not.
Warnings — ignored settings, empty commands, or a file intended for another host:
-
A file written in another host's nested shape: events under a top-level
hooksobject beside non-object metadata such asversion,descriptionor$schema. Without metadata, this finding requires all nested keys to name known events and at least one flat event to contain a group with no handler of its own. Other shapes receive the ordinary event and handler diagnostics..agents/is a directory name four ecosystems share, so a Claude, Codex or Cursor hooks file lands here often. One finding replaces the pile the ordinary walk would produce. Its severity is fixed at warning — a configuredseverity:does not reach it — because the shape says the file targets another tool, and an ERROR would fail CI for a repository that never configured Antigravity. Null siblings do not count as metadata. An object-valued sibling or a top-levelenabledalso keeps the ordinary named-hook validation. Ahooksobject holding onlyPreToolUsewith no metadata sibling is excluded: the two hosts' group shapes coincide there and the file really does dispatch. -
An event name Antigravity does not dispatch. Known names match case-insensitively, so
pretooluseis fine;SessionEndis not an event and its hooks never fire. - An unknown key on a handler or on a group.
env,cwd,nameandbackgroundare all discarded, so a hook written with one does not do what its author expects. - A command hook with no
command, or an empty one. It loads and runs nothing.
While an error stands, the warnings are held back: nothing in the file has loaded, so nothing has been ignored yet.
Struct field names match without regard to case: Enabled, Matcher,
Hooks, Command, Type, Prompt, Model and Timeout use the same
contracts as their canonical spellings. Hook names remain case-sensitive,
and handler type values still accept only lowercase command and prompt.
Command scans and generated documentation read the same decoded fields.
What is not reported¶
- The
matcherpattern. Antigravity never compiles it at load time — an unclosed character class loads clean — so no linter can say whether a given pattern will be accepted, and the regex engine is unverified.""and"*"are the documented catch-alls. - A hook-level
"enabled": false. It is the documented per-hook switch and a valid thing to commit. The security rules still read the commands under it, because the command ships in the repository either way. - A named hook called
enabled. With an object value it is an ordinary hook and loads, so it is checked like any other rather than reported. Only a non-object value there kills the file. - A
prompthook with noprompttext, and an empty group or event array. All load. - Valid repeated keys. Repeated hook names and events replace their
earlier values, including differently capitalized event keys. Handler
string fields apply in encounter order, but a later null retains their
previous string value. A null event or
hookslist clears that array. An earlier invalid type still rejects the file, even when the containing event or named hook is replaced. Handler type and command/prompt conflicts are checked after that handler's fields have been decoded, so replacing an unsupported type string with a supported one is accepted. - A null root. It is an explicit empty configuration, like
{}. - Finite numbers outside Python float range in ignored fields.
metadata: 1e400is valid JSON and receives only the ignored-key warning. The same value intimeoutstill fails its integer type check. - Null fields and empty strings are not type errors for string fields.
Null events and entries, an empty hook name, and empty
type,prompt,modelormatchervalues are accepted. A missing, null or emptycommandstill earns the no-command warning above. An empty string intimeoutorenabledis a type error and rejects the file.
Event names¶
Two shapes, and the event decides which:
PreToolUse,PostToolUse— an array of{matcher, hooks: [handler, …]}groups.PreInvocation,PostInvocation,Stop,SessionStart— a flat array of handlers. Amatcherwritten on one of these is ignored.
Examples¶
Bad — a fractional timeout, which costs every hook in the file:
{
"shell-audit": {
"PreToolUse": [
{
"matcher": "run_command",
"hooks": [
{ "type": "command", "command": "./scripts/audit-command.sh", "timeout": 1.5 }
]
}
]
},
"lint-on-stop": {
"Stop": [{ "command": "make lint" }]
}
}
Good — a tool matcher naming a tool Antigravity has, and a prompt hook
that carries no command:
{
"shell-audit": {
"PreToolUse": [
{
"matcher": "run_command",
"hooks": [
{ "type": "command", "command": "./scripts/audit-command.sh", "timeout": 5 }
]
}
],
"PostToolUse": [
{
"matcher": "*",
"hooks": [{ "command": "./scripts/record-tool-call.sh" }]
}
]
},
"timetable-reminder": {
"PreInvocation": [
{
"type": "prompt",
"prompt": "Departure times in this repository are UTC.",
"model": "gemini-3-pro"
}
]
}
}
How to fix¶
- Write
timeoutas a whole number of seconds inside the 32-bit range (5, not5.0,"5"or1099511627776). - Give a command hook a
commandand nothing else from the prompt side; give a prompt hook apromptand optionally amodel. - Match against Antigravity's own tool names —
run_command,view_file,write_to_file,replace_file_content,browser_*— and use""or"*"for every tool. - If the file was written for Claude, Codex or Cursor, move it to that
host's directory. Antigravity reads a map of named hooks —
{"audit": {"Stop": [...]}}— not events nested underhooks. - Move an
enabledkey inside the named hook it is meant to switch off. - Drop a key the parser discards, or move its value into the
commanditself.
If Antigravity adds an event newer than this skillsaw release, allow it in
.skillsaw.yaml. Extra events accept both grouped and flat handlers:
Configuration¶
| Parameter | Description | Default |
|---|---|---|
extra-events |
Additional hook event names to accept, for events newer than this skillsaw release | [] |
Run skillsaw explain antigravity-hooks-valid to see this documentation and the rule's effective configuration in your terminal.