What are hooks in Claude Code, Codex, Cursor and Grok Build?

By

Hooks run your own scripts when a coding agent calls a tool or stops. How hooks work in Claude Code, Codex, Cursor and Grok Build, with examples.

~~~

Hooks are commands that a coding agent runs on its own at fixed points in its work, like right before it calls a tool or when it stops responding. An instruction in a prompt is something the model may or may not follow. A hook runs every time its event fires.

How hooks work

A coding agent works in a loop. It reads your prompt, calls tools to read files, edit them and run commands, then writes an answer. Each step in that loop is an event, and a hook attaches your own script to an event.

When the event fires, the agent runs every hook registered for it and passes along a JSON description of what’s happening. For a tool call, that includes the tool’s name and its input, like the shell command it’s about to run or the file it’s about to edit.

Most hooks just observe. They format a file, write a log line or send a notification. A hook on the event before a tool call can also answer back and block the action.

Where hooks run in the agent loopBefore a tool runs, a PreToolUse hook receives the tool call as JSON. Exit code 2 blocks the call and sends the reason back to the agent. Otherwise the tool runs, a PostToolUse hook can format or log the result, and a Stop hook runs when the agent finishes its answer.exit 2exit 0Agent decides tocall a toolPreToolUse hookreceives the call asJSONCall blocked, reasongoes back to theagentTool runsPostToolUse hookformats or logsAgent finishes itsanswerStop hook notifiesor logs

Exit code 2 means “block” in all four tools covered here, and exit code 0 lets the action continue. In Claude Code, exit 0 doesn’t approve anything, so the call still goes through the normal permission checks.

Hooks are different from Git hooks, which run on Git operations like commits and pushes, whoever runs them. Agent hooks run on the agent’s own events, even when no Git command is involved.

Hooks in each tool

The four tools agree on the idea and on exit code 2. They differ in where the config lives, how events are named and what happens when a hook fails.

Claude CodeCodexCursorGrok Build
Project config.claude/settings.json.codex/hooks.json or [hooks] in .codex/config.toml.cursor/hooks.json.grok/hooks/*.json
Event namesPreToolUse, PostToolUse, StopPreToolUse, PostToolUse, StoppreToolUse, afterFileEdit, stopPreToolUse, PostToolUse, Stop
Block an actionExit 2, or JSON with permissionDecision: "deny"Exit 2, or JSON with permissionDecision: "deny"Exit 2, or JSON with "permission": "deny"Exit 2, or JSON with "decision": "deny"
Default timeout10 minutes for command hooks600 seconds for most hooksNot documented5 seconds
Also runs hooks fromNot documentedNot documentedClaude CodeClaude Code and Cursor

Claude Code

Claude Code reads hooks from its settings files: ~/.claude/settings.json for all your projects, .claude/settings.json for one project (you can commit it), and .claude/settings.local.json for your own overrides. Plugins, skills and subagents can define hooks too.

The config nests three levels deep: the event, a matcher group, then the hooks to run. A matcher like Edit|Write limits a hook to those tools, and it’s case-sensitive. Besides shell commands, a hook can call an HTTP endpoint, an MCP tool, a prompt or a subagent.

There are a lot of events. The ones you’ll reach for first are PreToolUse, PostToolUse, Notification (Claude is waiting for you), Stop, SessionStart and UserPromptSubmit.

A PreToolUse hook that answers deny blocks the tool in every permission mode, including bypassPermissions. Type /hooks in a session to browse what’s configured. See the Claude Code hooks guide.

OpenAI Codex

Codex reads hooks from a hooks.json file or from [hooks] tables in config.toml, in ~/.codex/ or in your repo’s .codex/ folder. A hook can run a command or call an MCP tool.

Every command hook receives one JSON object on stdin. To block a tool call, exit with code 2 and write the reason to stderr, or print a JSON answer with permissionDecision: "deny".

Codex tracks trust per hook. New or changed hooks are skipped until you review and trust them with /hooks in the CLI, and project hooks only load once the project’s .codex/ folder is trusted.

When several hooks match the same event, Codex starts them at the same time, so one hook can’t stop another from running. See the Codex hooks docs.

Cursor

Cursor reads .cursor/hooks.json in your project and ~/.cursor/hooks.json for your user. The file has a version key set to 1 and a hooks object, with a list of commands for each event.

Event names are camelCase, and some are specific to Cursor, like beforeShellExecution, beforeReadFile and afterFileEdit. A hook answers with a permission of allow, deny or ask, or exits with code 2 to block.

Hooks fail open by default. If a script crashes or times out, the action goes ahead. Set failClosed to true on a hook that must block when it fails.

Cursor also loads Claude Code hooks from .claude/settings.json, through a third-party setting that’s on by default. User-level hooks don’t run in cloud agents. See the Cursor hooks docs.

Grok Build

Grok Build reads personal hooks from ~/.grok/hooks/*.json and project hooks from .grok/hooks/*.json. It also reads Claude Code’s .claude/settings.json and Cursor’s .cursor/hooks.json, Cursor’s camelCase event names included. A hook runs a command or posts the event to a URL.

PreToolUse is the only event that can block. Exit 0 allows, exit 2 denies, and anything else (a timeout, a crash, bad output) lets the call through. The default timeout is 5 seconds.

Project hooks need your trust before they run, with /hooks-trust or by starting Grok with --trust. The input uses camelCase fields like toolName and toolInput. See the Grok Build hooks docs.

Details checked against each vendor’s documentation in October 2026.

A real example

Let’s format every file Claude Code edits with Prettier. Add this to .claude/settings.json in your project:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

The hook reads the JSON from stdin, pulls out the edited file’s path with jq, and runs Prettier on it. You need jq installed (brew install jq on a Mac).

Now a Stop hook. Say you run Events Logger, my self-hosted dashboard for app events, on localhost:4321. This script logs an event each time Claude Code finishes responding. Save it as .claude/hooks/log-turn.sh:

#!/bin/bash
curl -s -X POST http://localhost:4321/api/events \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ev_your-key-here" \
  -d '{"project": "q4q8nb18qc2i", "category": "agents", "title": "Claude Code finished a turn"}' > /dev/null
exit 0

Replace the key and the project ID with yours, then make it executable:

chmod +x .claude/hooks/log-turn.sh

Register it next to the formatter, inside the same hooks object:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/log-turn.sh"
          }
        ]
      }
    ]
  }
}

Stop doesn’t take a matcher, so the group has only hooks. Events Logger creates the agents category the first time an event arrives, and every finished turn shows up as an event in that project.

When to use hooks

Use a hook for anything that must happen every time: formatting after an edit, a quick lint, a notification when the agent waits for you. An instruction in AGENTS.md works most of the time. A hook works all of the time, as long as the script itself works.

Use a PreToolUse hook when a rule needs logic that permission rules can’t express, like checking a command’s arguments against a list kept in a file.

Use logging hooks when you want a record of what agents did across projects, without asking the agent to remember to write it.

Skip hooks for slow work. A hook that runs your whole test suite after every edit stalls the loop. Put slower checks on commit and push instead, the way my post on Git hooks for coding agents does.

Pitfalls

Most hooks fail open. In Grok Build, and in Cursor unless you set failClosed, a crash or a timeout lets the action through. In Claude Code, a timed-out PreToolUse hook doesn’t block the call. In Codex, an MCP tool hook that errors doesn’t block the operation, and some specialized tool paths skip hooks entirely. The Codex docs put it well: treat tool hooks as a useful guardrail, not a complete enforcement boundary. For hard limits, add deny rules to your permission settings too.

A hook can’t undo anything. PostToolUse runs after the tool already ran, so a check there can report a problem but not prevent it.

A command hook runs as you, with your full user permissions. Read the hooks in a repo before you trust it. In Claude Code, a claude -p run never shows the trust dialog and treats the folder as trusted, so hooks committed in that repo’s .claude/settings.json run.

Stop fires whenever Claude finishes responding, not only when a task is done. A log of Stop events counts turns, not finished tasks.

Hooks don’t port cleanly. Cursor and Grok Build read Claude Code’s hook files, but Grok documents camelCase input like toolInput. A script written for Claude Code’s tool_input may not find its data there, so test each hook in each tool you use.

  • Permission modes: Permission modes decide what a coding agent can do without asking. The modes in Claude Code, Codex, Cursor and Grok Build, and what YOLO mode really skips.
  • Subagents: A subagent is a helper your coding agent starts with its own context window. How subagents work in Claude Code, Codex, Cursor and Grok Build.
  • Context window: The context window is everything a coding agent can see at once. How to check it, free it and keep it small in Claude Code, Codex, Cursor and Grok Build.

All AI glossary entries →

Want me to talk about your product? You can sponsor this site.