Extension hook events
The normative correspondence between the neutral extension hook vocabulary (spec 0179, issue #1005) and each supported command-line tool's own event names. Every cell states either the target's own event name or an explicit no-counterpart marker — no row is omitted to avoid showing one, and the vocabulary is never presented as expressible on every target (R5).
This artifact is one of two independent representations the R6 agreement
check (scripts/check-extension-hook-map.sh) compares: this table, parsed as
data, against scripts/lib/extension-hooks.sh's translation, derived by
actually executing it against a synthetic probe extension. Editing either
side alone turns the check red — see that script's own header for why a
single shared source would make the check vacuous.
Correspondence table
| Neutral event | Claude Code | Gemini CLI | Copilot CLI | Antigravity CLI |
|---|---|---|---|---|
PreToolUse |
PreToolUse |
BeforeTool |
preToolUse |
PreToolUse |
UserPromptSubmit |
UserPromptSubmit |
BeforeAgent |
userPromptSubmitted |
no counterpart |
PreToolUse is the maps-everywhere anchor: every supported target expresses
a pre-tool-execution moment (spec 0179 -> Notes). UserPromptSubmit is the
maps-partially anchor: Claude, Gemini and Copilot each express a
user-prompt-submission moment; the Antigravity CLI's complete event set
(PreToolUse, PostToolUse, PreInvocation, PostInvocation, Stop) has
no counterpart for it — "none of them corresponds to a user-prompt
submission or to a session boundary" (spec 0179 -> Notes). Admitting a name
to this closed set requires at least one target to demonstrably expose a
counterpart (R4); no other neutral event has that evidence yet, so the set
stays at these two.
Neutral tool-class matcher
| Neutral class | Claude Code | Gemini CLI | Copilot CLI | Antigravity CLI |
|---|---|---|---|---|
shell |
Bash |
run_shell_command |
bash |
run_command |
A matcher is admissible only on an event whose counterpart accepts one on at
least one target (R8); of the two neutral events above, only PreToolUse is
matcher-accepting on any target. Omitting the matcher on a matcher-accepting
event renders as each target's own match-all form (R8, second sentence).
Per-target reference
Each column below records the hook file the target reads for an extension,
the tool version the column was grounded against, the probe method, the time
unit, and the extension-root token form — reconciled against
scripts/lib/extension-targets.json, the render's own copy of the same
facts (scripts/check-extension-hook-map.sh asserts the two agree; see that
script's header for why two places holding one fact need a check).
Claude Code
- Version probed: 2.1.241
- Probe method: the installed binary's own embedded reference text
(
strings -aon the resolved Mach-O executable), settling the R9 envelope divergence from the tool itself rather than from vendor prose or the incumbent builder's assumption. - Hook file:
hooks/hooks.jsonat the plugin root. - Structural form: envelope —
{"hooks": {"EVENT": [{"matcher": ..., "hooks": [{"type": "command", "command": ...}]}]}}. - Matcher form: tool name, exact or
|-alternation (e.g.Bash|Write). - Time unit: ungrounded — the embedded doc shows an inline
"timeout"field but never formalizes its unit. A declared time limit emits nothing on Claude rather than a guessed conversion (R10). - Extension-root form:
${CLAUDE_PLUGIN_ROOT}, substituted by the CLI's own per-element string templating (not a shell environment variable).
Gemini CLI
- Version probed: 0.46.0
- Probe method: documentation shipped in the installed bundle, plus the
bundle's own extension-hook loader (spec 0179's original authoring-time
grounding; the installed bundle is minified and does not survive a
plain-text re-grep, corroborated instead by the repository's own
hooks/gemini-transcript-hooks.json). - Hook file:
hooks/hooks.jsonin the extension directory, explicitly not the manifest. - Structural form: envelope —
{"hooks": {"EVENT": [{"matcher": ..., "hooks": [{"type": "command", "command": ...}]}]}}; a group with no intended tool filter omits thematcherkey entirely. - Matcher form: regex over tool names.
- Time unit: milliseconds, default 60000.
- Extension-root form:
${extensionPath}.
GitHub Copilot CLI
- Version probed:
1.0.80self-reported; resolved installed binary is1.0.49(both readings carried, per spec 0179's own Notes precedent). - Probe method: a live functional test — a synthetic plugin loaded
via
--plugin-dir, one real tool-invoking session, and a side-channel log the candidate hook command writes (scripts/probe-extension-hooks.sh,docs/runbooks/extension-hook-probe.md). No vendor documentation states any of the fields below; every one is grounded by observing the installed tool actually fire (or not fire) a hook. This is the R12 branch point: the probe verdict is B1A (a plugin-level hook surface exists andpreToolUsefires within the grounded intersection). - Hook file:
hooks.jsonat the plugin root. (hooks/hooks.jsonin a subdirectory was independently observed to fire too; the render targets the root form as the single canonical delivery path — see the runbook's Results section.) - Structural form: flat —
{"version": 1, "disableAllHooks": false, "hooks": {"EVENT": [{"type": "command", "matcher": ..., "command": ...}]}}; each handler carries its own optionalmatcherinline, unlike the grouped shape the other three targets use. - Matcher form: regex over tool names — confirmed live:
matcher: "no-such-tool-xyz"did not fire,matcher: "bash"did,matcher: ".*"fired unconditionally. The permission-flag tool-name space (--allow-tool='shell(...)') is a DIFFERENT vocabulary:matcher: "shell"did not match. - Time unit: seconds. Confirmed live by two consistent data points:
timeout: 1against a 3-second sleep was killed before completion;timeout: 2000against the same sleep was not — only a seconds reading is consistent with both. - Extension-root form:
${COPILOT_PLUGIN_ROOT}, exported by the CLI as a real environment variable into the hook command's shell (confirmed live:$COPILOT_PLUGIN_ROOTand$(pwd)both resolved to the plugin's own directory).
Antigravity CLI
- Version probed: 1.1.19
- Probe method: the vendor hook and plugin contract shipped on disk with
the CLI (
~/.gemini/antigravity-cli/builtin/skills/agy-customizations/docs/hooks.md), re-read live and matching spec 0179's original grounding exactly. - Hook file:
hooks.jsonat the plugin root. - Structural form: a map of named hooks; tool events (
PreToolUse,PostToolUse) are grouped under amatcher+hookswrapper, lifecycle events (PreInvocation,PostInvocation,Stop) are flat lists of handler objects. OnlyPreToolUseis in the current neutral vocabulary, so only the grouped form is exercised by the render today. - Matcher form: regex over tool names.
- Time unit: seconds, default 30.
- Extension-root form: none — no path variable exists; the documented
rule is that a handler's working directory is the directory holding
hooks.json, so the render emits a working-directory-relative command with the neutral token stripped rather than substituted.