Hooks Reference
July 1, 2026 · View on GitHub
git-courer installs lifecycle hooks into MCP clients so agents always see the git-courer Golden Rules and are nudged toward the git-courer MCP tools instead of raw git.
This is the dedicated hooks reference. For the per-client setup overview, see mcp-clients.md.
Event Types
git-courer uses four hook events. Each event has a matcher (which agent actions it fires on), a command (which git-courer subcommand is invoked), and a purpose.
| Event | Matcher | Command | Fires When | Purpose |
|---|---|---|---|---|
PreToolUse | Bash (Claude Code, Codex) / run_command (Antigravity) | git-courer hook-check | The agent is about to run a shell command | Classify the command; if it's a covered git subcommand, emit a deny decision pointing the agent at the git-courer MCP tool. Denies covered git subcommands with a message pointing to the equivalent git-courer MCP tool. |
SessionStart | startup|resume | git-courer session-start-hook | A new agent session starts (or resumes) | Inject the Golden Rules into the agent's context so the workflow is known from the first turn. |
SubagentStart | general-purpose|Explore|Plan | git-courer subagent-start-hook | A subagent is spawned | Inject the Golden Rules into the subagent's context, so subagents also follow session start → status → diff/review → pr-review. |
PreInvocation | (empty matcher) | git-courer pre-invocation-hook | Before every model call (Antigravity only) | Inject the Golden Rules before each model invocation. Compensates for clients that have no SessionStart/SubagentStart events. |
What each hook emits
Every hook subcommand emits a JSON object on stdout shaped for the Codex hook contract:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"additionalContext": "..."
}
}
hookEventNameechoes the event (PreToolUse,SessionStart,SubagentStart,PreInvocation).additionalContextis the Golden Rules markdown for the three context-injection hooks. ForPreToolUseit is a short suggestion likeUse git-courer/<tool> instead of bash <command>when the command is a git command; for non-git commands,hook-checkexits cleanly with no output (no decision is emitted, no command is denied).permissionDecision/permissionDecisionReasonare set to'deny'for covered git subcommands.
Per-Client Differences
Clients store hooks differently and support different event subsets. git-courer adapts to each.
Claude Code — inline hooks in settings.json
- Storage: hooks are stored inline in
~/.claude/settings.jsonunder the top-levelhooksobject, keyed by event name. There is no separate hooks file. - Form: Claude Code uses the exec form —
type: "command"with separatecommandandargsfields, plus an optionaltimeout(seconds). git-courer setstimeout: 10onSessionStart,SubagentStart, andUserPromptSubmit. - Event names: Claude Code calls the
PreInvocationeventUserPromptSubmit. git-courer installs it under that name. - Events installed:
PreToolUse(matcherBash),SessionStart(startup|resume),SubagentStart(general-purpose|Explore|Plan),UserPromptSubmit(empty matcher). - Merge: existing non-git-courer hooks and every other top-level settings key (
permissions,model,theme, …) are preserved. Matching is by(matcher, command containing "git-courer")— same matcher + git-courer command is updated in place (handles binary path changes); same matcher + no git-courer command appends git-courer alongside the existing hook. - Write: atomic (temp file + rename).
- Backup:
~/.claude/settings.json→~/.claude/settings.json.bak.
Codex — separate hooks.json
- Storage: hooks are stored in a separate
~/.codex/hooks.jsonfile (not insideconfig.toml). - Form: simple
{ "type": "command", "command": "..." }entries, no timeout field. - Events installed: all four —
PreToolUse(matcherBash),SessionStart(startup|resume),SubagentStart(general-purpose|Explore|Plan),PreInvocation(empty matcher). - Stdin mode: when
git-courer hook-checkis invoked with no args and stdin is a pipe, it reads Codex hook JSON from stdin (event.input.command), classifies it, and emits a Codex-shapedhookSpecificOutput. - Backup:
hooks.json→hooks.json.bak.
Antigravity — separate hooks.json + declarative permissions
- Storage: hooks in
~/.gemini/antigravity-cli/hooks.jsonAND declarative permissions in~/.gemini/antigravity-cli/settings.json(two separate files). - Matcher difference: Antigravity uses the
run_commandmatcher (notBash) forPreToolUse. - Events installed: only two —
PreToolUse(run_command) andPreInvocation(empty matcher). Antigravity has noSessionStart/SubagentStartevents, soPreInvocationruns before every model call to inject the Golden Rules. - Permissions (
settings.json): three entries are merged into the existingpermissionsobject:permissions.allowgains"mcp(git-courer/*)"— allow all git-courer MCP tools without prompting.permissions.askgains"command(git *)"— prompt before raw git commands.permissions.askgains"command(*)"— prompt before any other shell command.
- Backup:
hooks.json→hooks.json.bak;settings.json→settings.json.gc.bak(distinct suffix to avoid colliding with any Antigravity-native backup scheme).
OpenCode — no hooks (policy + prompt block instead)
- No hooks: OpenCode does not support lifecycle hooks. git-courer instead injects:
permission.bash["git {sub}"] = "deny"(granular, one entry per covered subcommand) plus"git *": "ask"as a fallback intoopencode.json— OpenCode denies the 23 covered git subcommands and asks before any other raw git command.- The
AGENTS.mdpath into theinstructionsarray — so OpenCode reads the Golden Rules from the prompt block it already loads.
- See mcp-clients.md § OpenCode Policy Injection.
Cursor
- Cursor is not auto-configured by
git-courer mcp setup. If you use Cursor, add the MCP server manually using the standard JSON format (see mcp-clients.md § Config Formats). Cursor does not expose a lifecycle hook system git-courer can target, so no hooks or prompt block are installed for it.
Lifecycle
Install
git-courer mcp setup (or mcp setup <client>) installs hooks for every detected client that supports them:
- Backup any existing hooks/settings file before the first mutation.
- Merge git-courer hook entries into the existing hooks object, preserving every non-git-courer hook.
- Write the merged file atomically where the client supports it (Claude Code, Antigravity).
Install is idempotent — running it twice produces byte-identical output. The backup is not overwritten on re-run.
Status
git-courer doctor reports the hook status per client:
installed— all git-courer hook events are present with a git-courer command for the expected matcher.not_installed— none of the git-courer hook events are present.partial(Claude Code only) — some but not all git-courer hook events are present. Re-rungit-courer mcp setup <client>to complete the install.
Remove
git-courer remove cleans up hooks for every known client (even clients whose binary is no longer on PATH):
- If a
.bak(or.gc.bak) exists, it is restored over the live file and the backup is removed. - Otherwise, git-courer strips only the git-courer-owned entries in place, preserving non-git-courer hooks and every other settings key.
Remove is idempotent — running it twice does not error and does not touch user content.
Configuration Examples
Claude Code (~/.claude/settings.json)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "/usr/local/bin/git-courer hook-check", "args": [] }
]
}
],
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{ "type": "command", "command": "/usr/local/bin/git-courer session-start-hook", "args": [], "timeout": 10 }
]
}
],
"SubagentStart": [
{
"matcher": "general-purpose|Explore|Plan",
"hooks": [
{ "type": "command", "command": "/usr/local/bin/git-courer subagent-start-hook", "args": [], "timeout": 10 }
]
}
],
"UserPromptSubmit": [
{
"matcher": "",
"hooks": [
{ "type": "command", "command": "/usr/local/bin/git-courer pre-invocation-hook", "args": [], "timeout": 10 }
]
}
]
}
}
Codex (~/.codex/hooks.json)
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "/usr/local/bin/git-courer hook-check" }] }
],
"SessionStart": [
{ "matcher": "startup|resume", "hooks": [{ "type": "command", "command": "/usr/local/bin/git-courer session-start-hook" }] }
],
"SubagentStart": [
{ "matcher": "general-purpose|Explore|Plan", "hooks": [{ "type": "command", "command": "/usr/local/bin/git-courer subagent-start-hook" }] }
],
"PreInvocation": [
{ "matcher": "", "hooks": [{ "type": "command", "command": "/usr/local/bin/git-courer pre-invocation-hook" }] }
]
}
}
Antigravity
~/.gemini/antigravity-cli/hooks.json:
{
"hooks": {
"PreToolUse": [
{ "matcher": "run_command", "hooks": [{ "type": "command", "command": "/usr/local/bin/git-courer hook-check" }] }
],
"PreInvocation": [
{ "matcher": "", "hooks": [{ "type": "command", "command": "/usr/local/bin/git-courer pre-invocation-hook" }] }
]
}
}
~/.gemini/antigravity-cli/settings.json:
{
"permissions": {
"allow": ["mcp(git-courer/*)"],
"ask": ["command(git *)", "command(*)"]
}
}
Verifying Your Installation
git-courer doctor
Check the Hooks installed and Claude hooks rows in the report. If either shows not_installed or partial for a client you use, re-run:
git-courer mcp setup <client>
To manually verify a hook is wired, run the subcommand directly — it should emit JSON on stdout:
/usr/local/bin/git-courer hook-check "git status"
/usr/local/bin/git-courer session-start-hook < /dev/null