I tried moving a small safety check from Claude Code to Cursor this week.

The hook blocked rm -rf and a few other commands I do not want an agent running without a second look. The script itself was simple. The frustrating part was that none of the names carried over.

Claude Code calls the event PreToolUse. Cursor uses preToolUse. Windsurf, Copilot, Gemini CLI, Codex, Cline, and OpenCode each have their own names and configuration formats.

I initially assumed I had misspelled a JSON key. I had not. The tools just use different vocabulary for roughly the same idea.

Hooks are useful when I want something deterministic around an agent action:

  • block a command before it runs
  • log tool calls
  • format or lint files after an edit
  • run a little setup or cleanup at the beginning or end of a session

The exact event names differ, but I now think about hooks in three buckets.

Before an action
The agent is about to run a command, modify a file, or call a tool. This is where a hook can inspect the request and deny it.

After an action
The action already happened. These hooks are useful for logging, formatting, linting, or sending a notification. They are not a rollback mechanism.

At the session boundaries
Some agents offer hooks for session start, stop, end, or compaction. These are useful for initializing context, refreshing an index, or recording what happened during a run.

One thing that was not immediately obvious to me: there is usually no special “commit hook” in an agent.

From the agent’s perspective, this:

bash

git commit -m "..."

is just a shell command. If I want to intervene before it runs, I match the command text in the same pre-tool hook I would use for npm test or ls.

Here is a Claude Code hook that blocks a commit unless a simple test-pass marker exists.

.claude/settings.json

json

{  "hooks": {    "PreToolUse": [      {        "matcher": "Bash",        "hooks": [          {            "type": "command",            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/gate-commit.sh"          }        ]      }    ]  }}

.claude/hooks/gate-commit.sh

bash

#!/bin/bash
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q '^git commit' && [ ! -f .last-test-pass ]; then  jq -n '{    hookSpecificOutput: {      hookEventName: "PreToolUse",      permissionDecision: "deny",      permissionDecisionReason: "Run tests before committing"    }  }'else  exit 0fi

This is an agent-level check, not a replacement for Git hooks. If I need the rule to hold for every commit—including ones I make myself—I would still add a real .git/hooks/pre-commit hook or enforce it in CI.

Here is the cheat sheet I keep for the agents I have been looking at:

AgentBeforeAfterSession startSession end / stopConfig
Claude CodePreToolUsePostToolUseSessionStartStop / SessionEnd.claude/settings.json
CursorpreToolUsepostToolUsesessionStartstop / sessionEnd.cursor/hooks.json
Windsurf Cascadepre_run_commandpost_run_commandpre_user_promptpost_cascade_response.windsurf/hooks.json
GitHub CopilotpreToolUsepostToolUsesessionStartsessionEnd / agentStop.github/hooks/*.json
Gemini CLIBeforeToolAfterToolSessionStartSessionEndsettings.json
ClinePreToolUsePostToolUseTaskStartTaskCancel.clinerules/hooks/
Codex CLIPreToolUsePostToolUse~/.codex/hooks.json
OpenCodetool.execute.beforetool.execute.aftersession.createdsession.idle.opencode/plugin/*.ts

Two implementation details I found worth remembering:

  • In Codex CLI, the pre-tool event only covers shell actions, so it is not a general guardrail for edits or MCP calls.
  • OpenCode works more like an event bus than a conventional hooks file. Its plugin can subscribe to many events, and session.idle is the closest equivalent to a stop hook.

The practical lesson for me was simple: do not try to memorize every hooks API.

Work out whether the behavior belongs before an action, after an action, or at a session boundary. Then look up what that particular agent calls it.

HOW TO : Use hooks in coding agents

Every coding agent has hooks. None of them agree on what to call them. Instead of memorizing eight APIs, I think in three buckets: before an action, after an action, and at the session boundaries.