{"slug": "agent-hooks-in-claude-code-cursor-and-codex-explained", "title": "Agent hooks in Claude Code, Cursor, and Codex explained", "summary": "Claude Code, Cursor and Codex each support agent hooks — scripts the tool runs automatically at fixed moments such as before a shell command or when the agent finishes — and hooks are more reliable than instructions in AGENTS.md files because they execute around the model rather than through it, according to a comparison by Antoine van der Lee. The three tools share the same event-trigger-script-action model but differ in configuration: Claude Code uses ~/.claude/settings.json and .claude/settings.json with PreToolUse, PostToolUse and Stop events, Cursor uses ~/.cursor/hooks.json and .cursor/hooks.json with preToolUse, beforeShellExecution, postToolUse, afterFileEdit and stop, and Codex uses ~/.codex/hooks.json or config.toml with PreToolUse, PostToolUse and Stop. All three block a call via exit code 2 or a JSON deny decision, and a failing hook lets the call proceed unless Claude Code's onFailure is set to \"block\" or Cursor's failClosed is true.", "body_md": "Agent hooks are small scripts that your agent tool runs automatically when something happens, like right before a shell command or when the agent finishes. Claude Code hooks, Cursor hooks, and Codex hooks all work this way, and they are the most reliable way to make sure a rule is followed every time.\n\nI use hooks to keep Xcode build logs out of my agent’s context, or to compound learnings automatically. Before, I relied on `AGENTS.md` files, but in my experience, hooks are way more reliable. In this article, I’ll explain how agent hooks work, how the three tools differ, and how you can set one up yourself.\n\n## \n\nNot long ago, I relied on a rule in my global instructions that told my agent to filter Xcode build output, so it would only see errors, warnings, and test results. It’s a good rule, but it’s still a sentence the agent reads. An agent can forget what it reads, and I experienced that myself: it wasn’t reliable at all.\n\nEverything in `AGENTS.md`, agent skills, and your global instructions ends up in the context of your agent. The agent decides what to do with it, and it usually follows it, but it doesn’t have to. I wrote about [moving rules into reusable skills](https://www.avanderlee.com/ai-development/agent-skills-replacing-agents-md-with-reusable-ai-knowledge/) before, and the [guide on writing an AGENTS.md](https://aifundamentals.dev/articles/how-to-write-agents-md/) explains what belongs in there. Neither of them can guarantee a rule is followed.\n\nA hook works differently:\n\n- **An instruction.**\nThe agent reads it and can ignore or forget it. It works through the model.\n- **A hook.**\nYour tool runs a script at a fixed moment, every time. It works around the model.\n\nThis means hooks are for the rules that only pay off when they are followed every time. The instruction still explains what you want, and the hook makes sure it happens.\n\n## \n\nIf you know webhooks, the idea is similar: something happens, and something you defined runs. A webhook sends an HTTP request to a URL, while an agent hook most commonly runs a script on your own machine. Every hook has four parts:\n\n- **The event.**\nA defined moment in the agent’s work, like the agent being about to run a shell command, a turn that ended, or a session that started.\n- **The trigger.**\nA filter on that event. A hook that only cares about shell commands ignores everything else.\n- **The hook.**\nYour script. It receives the details of the event as JSON on stdin.\n- **The action.**\nWhat your script asks the tool to do with the result, like adding context, changing the command, or denying it.\n\nA rule of thumb: a hook that runs before something happens can usually block or change it. A hook that runs after can’t undo what already happened. This makes sense if you think about it, but it’s a great distinction that helps me remember the differences.\n\n## \n\nThe idea is the same in all three tools, but names and configuration files differ. Learn the idea once, and check the documentation of your tool for the exact details: [Claude Code hooks](https://code.claude.com/docs/en/hooks), [Cursor hooks](https://cursor.com/docs/hooks), and [Codex hooks](https://developers.openai.com/codex/hooks).\n\n|  | Claude Code | Cursor | Codex | \n|---|---|---|---|\n| User configuration | `~/.claude/settings.json` | `~/.cursor/hooks.json` | `~/.codex/hooks.json` or`config.toml` | \n| Project configuration | `.claude/settings.json` | `.cursor/hooks.json` | `.codex/hooks.json` or`config.toml` | \n| Before a tool runs | `PreToolUse` | `preToolUse` ,`beforeShellExecution` | `PreToolUse` | \n| After a tool ran | `PostToolUse` | `postToolUse` ,`afterFileEdit` | `PostToolUse` | \n| Agent finished | `Stop` | `stop` | `Stop` | \n| Block a call | Exit code 2 or JSON `permissionDecision: \"deny\"` | Exit code 2 or JSON `permission: \"deny\"` | Exit code 2 or JSON `permissionDecision: \"deny\"` | \n| Hook fails | Call goes ahead, unless `onFailure` is`\"block\"` | Call goes ahead, unless `failClosed` is`true` | Reported as a hook failure, so test what your event does | \n\nThere are a few more differences worth knowing:\n\n- Claude Code and Codex use the same configuration shape: an event, a matcher, and a list of handlers. Cursor uses a flatter `hooks.json` with a`version` and a`command` per hook.\n- The tool you filter on is named `Bash` in Claude Code and Codex, and`Shell` in Cursor.\n- Codex asks you to review and trust a hook before it runs, and it only loads project hooks from a trusted project. Cursor also runs project hooks only in a trusted workspace.\n- All three tools have more events than the table shows, like the start and end of a session, before a prompt is sent, and before and after a subagent runs.\n\n## \n\nRaw `xcodebuild` output is long, and your agent rarely needs all of it. I covered the numbers in [How to reduce token usage in Claude Code, Codex, and Cursor](https://www.avanderlee.com/ai-development/reduce-token-usage-claude-code-codex-cursor/). Piping the build through [xcsift](https://github.com/ldomaradzki/xcsift) keeps errors, warnings, and test results and drops the rest.\n\nI started creating this hook after I found out that my raw build logs were often still ending up in my context. I needed a more reliable way to filter `xcodebuild` output, without maintaining scripts across all my projects.\n\nMy own hook rewrites the command in Cursor and Claude Code. The version below is simplified: it blocks a raw build and tells the agent how to run it instead, which works the same way in all three tools. Save it as `~/.agents/hooks/require-xcsift.sh` and make it executable with `chmod +x`:\n\n``` bash\n#!/bin/bash\ninput=$(cat)\ncommand=$(jq -r '.tool_input.command // empty' <<< \"$input\")\n\n# Only act on raw build and test commands that aren't filtered yet.\nif [[ \"$command\" == *xcodebuild* \\\n   && \"$command\" =~ (^|[[:space:]])(build|test)([[:space:]]|$) \\\n   && \"$command\" != *xcsift* ]]; then\n  message=\"Run it again as: set -o pipefail; $command 2>&1 | xcsift -f toon -w\"\n\n  # Cursor answers with JSON, Claude Code and Codex read stderr after exit code 2.\n  if jq -e 'has(\"cursor_version\")' <<< \"$input\" > /dev/null; then\n    jq -n --arg message \"$message\" '{permission: \"deny\", agent_message: $message}'\n    exit 0\n  fi\n\n  echo \"$message\" >&2\n  exit 2\nfi\n\nexit 0\n```\n\nThe script leaves everything else alone, including commands that already use `xcsift` and `xcodebuild -list`. If `jq` is missing or the input looks unexpected, it exits without blocking anything. That’s called **failing open**, and it’s the right choice for a convenience hook. A broken hook shouldn’t stop you from working.\n\nRegister it in Claude Code in `~/.claude/settings.json`. Codex uses the same shape in `~/.codex/hooks.json`:\n\n```\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"~/.agents/hooks/require-xcsift.sh\",\n            \"timeout\": 5\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nIn Cursor, add it to `~/.cursor/hooks.json`:\n\n```\n{\n  \"version\": 1,\n  \"hooks\": {\n    \"preToolUse\": [\n      {\n        \"command\": \"~/.agents/hooks/require-xcsift.sh\",\n        \"matcher\": \"Shell\",\n        \"timeout\": 5\n      }\n    ]\n  }\n}\n```\n\nYou don’t need to write this configuration by hand. Your agent can read the documentation of your tool and do the setup. Ask it to explain before it writes:\n\n```\nI want to add a hook to the agent tool I'm using right now.\nFirst read the hooks documentation for this tool and tell me which events it supports.\nThen help me with this idea: <describe the event and what should happen>.\nPropose the event, the trigger, and the script before you write anything.\nMake the script fail open, give it a short timeout, and explain how I can test it.\nDon't enable it before I've read the script.\n```\n\n## \n\nHooks come in different shapes. These are the other ones on my machine:\n\n- **The compound hook.**\n It runs when the agent finishes a turn. After at least 10 finished turns and at least two hours since the last run, it sends a follow-up message that sends the agent to update`AGENTS.md` . It can only add a message, and it can’t block anything.\n- **Worktree cleanup.**\n Every agent worktree builds into its own DerivedData folder, so old worktrees take up disk space. Hooks check for old worktrees when a session starts and after a shell command that mentions worktrees. Today, the cleanup only reports what it would remove. My tool has no event for removing a worktree, so a scheduled job runs the same script every hour and the hooks only make it faster. I wrote about the disk space side in[How to free up Xcode disk space safely with an AI agent](https://www.avanderlee.com/ai-development/how-to-free-up-xcode-disk-space-safely-with-an-ai-agent/) .\n- **A session recorder.**\nIn one project, a hook runs when a session ends and writes a small record of the session to a file, so I can analyze my sessions later. It doesn’t change anything about the agent’s work.\n\nDocumentation often lists formatters after edits, blocks on dangerous commands, and notifications when the agent needs you. Most of those are not hooks I run myself. I think that says something: I often tell myself that a task only costs ten seconds, so I don’t automate it. But once you notice you do something regularly, a hook is a way to automate it without having to remember it.\n\n## \n\nA hook is a script that runs on its own, with your permissions. That makes it powerful, and it needs some care:\n\n- **Read before you enable.**\nA hook that comes with a repository or a plugin is a script that runs on your machine. Treat it like any other script you download.\n- **Choose fail open or fail closed on purpose.**\n My build hook fails open. A hook that guards something dangerous should deny when it breaks, which you can configure with`failClosed` in Cursor and`onFailure` in Claude Code. In Claude Code, a hook that times out doesn’t block the tool call either unless you ask it to.\n- **Keep hooks fast.**\nThe agent waits for the hook. The hooks I configured at user level all have a timeout of five seconds.\n- **Limit hooks that keep the agent going.**\n A hook that sends a follow-up message at the end of a turn can start itself again. Cursor limits stop hooks with`loop_limit` , which defaults to 5. Check what your tool does.\n- **Don’t treat a hook as the only guard.**\n Some paths skip hooks. In Codex, for example, a`PreToolUse` hook doesn’t run again when the agent polls a command that already passed it.\n\n## \n\n### What are agent hooks?\n\nAgent hooks are scripts that your agent tool runs automatically when a certain event happens, like before a shell command runs or when the agent finishes its turn. The agent is not asked whether it wants to run them, so a hook works even when the agent would have forgotten a rule.\n\n### What is the difference between a hook and AGENTS.md?\n\nAGENTS.md is read by the agent as context, and the agent decides what to do with it. A hook is code that your tool runs at a fixed moment. Use AGENTS.md to explain what you want, and a hook for the rules that have to be followed every single time.\n\n### Is a hook the same as an agent skill?\n\nNo. A skill is something you or the agent calls by name or by description. A hook is triggered by an event, without anyone calling it.\n\n### Can a hook block an agent action?\n\nYes. A hook that runs before a tool call can usually block or change it. In Claude Code, Cursor, and Codex, a hook that exits with code 2 blocks the call. A hook that runs after something happened can't undo it.\n\n## \n\nInstructions are suggestions, and hooks are what actually rewrite or block an action. Every hook has an event, a trigger, a script, and an action. The names and configuration files differ between Claude Code, Cursor, and Codex, so link to the documentation of your tool and let your agent do the setup. Start with something you repeat, read every script before you enable it, and fail open unless the hook guards something dangerous.\n\nIf you want to learn where each rule should live in your setup, the [AI Fundamentals course](https://aifundamentals.dev?utm_source=swiftlee&utm_medium=article&utm_campaign=agent-hooks) covers `AGENTS.md`, skills, hooks, and MCPs step by step in module 6.\n\nFeel free to contact me or [tweet me on Twitter](https://twitter.com/twannl) if you have any additional tips or feedback. Which rule does your agent forget most often?\n\nThanks!", "url": "https://wpnews.pro/news/agent-hooks-in-claude-code-cursor-and-codex-explained", "canonical_source": "https://www.avanderlee.com/ai-development/agent-hooks-claude-code-cursor-codex/", "published_at": "2026-11-02 10:13:46+00:00", "updated_at": "2026-11-02 10:21:27.336469+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-products"], "entities": ["Claude Code", "Cursor", "Codex", "OpenAI", "Anthropic", "Antoine van der Lee", "Xcode"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/agent-hooks-in-claude-code-cursor-and-codex-explained", "markdown": "https://wpnews.pro/news/agent-hooks-in-claude-code-cursor-and-codex-explained.md", "text": "https://wpnews.pro/news/agent-hooks-in-claude-code-cursor-and-codex-explained.txt", "jsonld": "https://wpnews.pro/news/agent-hooks-in-claude-code-cursor-and-codex-explained.jsonld"}}