Cursor hooks are scripts that run at set points in the agent loop, such as before a shell command, after a file edit or when the agent stops. You define them in a hooks.json file (.cursor/hooks.json in a project or ~/.cursor/hooks.json for every project), and each hook talks to Cursor over stdin and stdout using JSON, so it can watch, block or change what the agent does.
Cursor's own summary: "Hooks let you observe, control, and extend the agent loop using custom scripts." This guide covers where the files go, which events exist, how blocking works and how hooks behave in cloud agents, based on Cursor's hooks documentation read on 24 October 2026.
What can you do with Cursor hooks?
The docs list these uses:
- Run formatters after edits
- Add analytics for events
- Scan for PII or secrets
- Gate risky operations, such as SQL writes
- Control subagent (Task tool) execution
- Inject context at session start
Rules and skills tell the agent what you want. Hooks enforce it. A rule can ask the agent not to run git push --force; a hook can refuse the command every time, whatever the model decides. That makes hooks the right tool for security policy, audit logs and anything that must happen on every edit.
Where do Cursor hooks live?
Cursor loads hooks.json from several places, and all matching hooks from every source run:
| Source | Location | Runs from |
|---|---|---|
| Enterprise | /Library/Application Support/Cursor/hooks.json (macOS), /etc/cursor/hooks.json (Linux and WSL), C:\ProgramData\Cursor\hooks.json (Windows) |
The enterprise config directory |
| Team | Configured in the Cursor web dashboard (Enterprise only) | The managed hooks directory |
| Project | <project-root>/.cursor/hooks.json |
The project root |
| User | ~/.cursor/hooks.json |
~/.cursor/ |
Hooks from installed plugins come after all of these, and plugin hooks run command hooks only.
The working directory matters for paths. In a project file, write .cursor/hooks/format.sh, not ./hooks/format.sh, because project hooks run from the project root. In your user file, ./hooks/format.sh points to ~/.cursor/hooks/format.sh. Project hooks also need a trusted workspace before they run.
Cursor watches these files and reloads them when you save, so you rarely need a restart.
How do you write your first Cursor hook?
Here is the quickstart pattern from the docs: a project hook that runs a script after every agent file edit.
{
"version": 1,
"hooks": {
"afterFileEdit": [{ "command": ".cursor/hooks/format.sh" }]
}
}
#!/bin/bash
# .cursor/hooks/format.sh: read the JSON input, do something, exit 0
cat > /dev/null
exit 0
Make the script executable with chmod +x .cursor/hooks/format.sh. The version field is required and should be 1. Each event name maps to an array of hook definitions, so you can chain several scripts on one event.
Each definition accepts these options:
| Option | What it does |
|---|---|
command |
Script path or shell command (required) |
type |
"command" (default) or "prompt" |
timeout |
Timeout in seconds |
matcher |
Regex that decides when the hook runs; empty or "*" matches everything |
failClosed |
When true, a crash, timeout or missing output blocks the action instead of letting it through |
loop_limit |
Cap on automatic follow-ups for stop and subagentStop hooks; default 5 |
Which events can Cursor hooks listen to?
Cursor groups hook events by what triggers them.
Agent hooks fire during an agent session: sessionStart, sessionEnd, preToolUse, postToolUse, postToolUseFailure, subagentStart, subagentStop, beforeShellExecution, afterShellExecution, beforeMCPExecution, afterMCPExecution, beforeReadFile, afterFileEdit, beforeSubmitPrompt, preCompact, stop, afterAgentResponse and afterAgentThought.
Tab hooks fire for inline completions: beforeTabFileRead and afterTabFileEdit. Keeping these separate lets you apply a stricter policy to autonomous Tab reads than to agent work you directed.
App lifecycle hooks fire outside any session. workspaceOpen runs when Cursor opens a workspace and on every workspace folder change, and can return extra plugin paths to load.
A few events are worth knowing in detail:
preToolUsefires for every tool (Shell, Read, Write, MCP, Task and more). It can deny the call or returnupdated_inputto rewrite it, for example turningnpm installintonpm ci.beforeSubmitPromptruns after you press send and before the request goes to Cursor's backend. Return"continue": falseto stop a prompt, for instance one that contains a secret.stopruns when the agent loop ends. If it returns afollowup_message, Cursor submits that text as your next message, which lets you build "keep going until the tests pass" loops, capped byloop_limit.sessionStartis fire and forget. It can returnenvvariables for later hooks andadditional_contextfor the conversation.
How do Cursor hooks block or allow an action?
Command hooks read JSON from stdin and write JSON to stdout. Permission hooks such as beforeShellExecution, beforeMCPExecution, beforeReadFile and preToolUse answer with a permission field.
{
"permission": "deny",
"user_message": "Force pushes are blocked in this repo.",
"agent_message": "Do not force push. Open a pull request instead."
}
Exit codes follow simple rules:
- Exit
0: Cursor uses your JSON output. For permission hooks, invalid JSON or output that does not match the schema blocks the action. - Exit
2: block the action, the same as returning"permission": "deny". The docs note this matches Claude Code behavior. - Any other exit code: the hook failed and the action proceeds. Hooks fail open by default; set
failClosed: truefor security checks.
When several hooks answer, Cursor merges them: deny beats ask, and ask beats allow, whatever the source. Note that ask is not enforced for preToolUse today, and subagentStart treats ask as deny.
Matchers keep hooks cheap. For beforeShellExecution the regex is tested against the full command, so "matcher": "curl|wget" only runs your network check when those tools appear. For preToolUse it is tested against the tool type, such as Shell, Read, Write or MCP:<tool_name>.
What are prompt-based hooks?
Instead of a script, a hook can ask a fast model to judge a condition written in plain language:
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{
"type": "prompt",
"prompt": "Does this command look safe to execute? Only allow read-only operations.",
"timeout": 10
}
]
}
}
The model returns { ok, reason }. A $ARGUMENTS placeholder is replaced with the hook input, and an optional model field overrides the default model. Prompt hooks are quick to write, but a script gives you a predictable answer, so keep hard security rules in command hooks.
Do Cursor hooks work in cloud agents?
Partly. Cloud agents run command-based hooks from .cursor/hooks.json in your repository, and on Enterprise plans they also run team and enterprise hooks. Supported events include preToolUse, postToolUse, beforeShellExecution, afterFileEdit, beforeSubmitPrompt, subagentStart, subagentStop, preCompact and stop.
These do not run in cloud agents: sessionStart, sessionEnd, beforeMCPExecution, afterMCPExecution, the Tab hooks and workspaceOpen. User hooks in ~/.cursor/hooks.json are also skipped, because the cloud VM has no access to your home folder, and prompt-based hooks are not supported there. Hooks also do not run during early read-only exploration turns; they start once the agent has a writable environment.
If you need a policy to apply everywhere, put it in the project file and commit it.
Can Cursor run Claude Code hooks?
Yes. Cursor's third-party hooks page says Claude Code hooks load when Include Third-Party Plugins, Skills, and Other Configs is on in Cursor Settings, under Agents, Third-Party Imports. It is on by default. Cursor reads .claude/settings.local.json, .claude/settings.json and ~/.claude/settings.json, maps names like PreToolUse to its own events, and accepts both Claude Code's nested hookSpecificOutput response and its own flat format. In Cursor's priority order, its own project and user hooks rank above the Claude Code files.
Hook scripts also receive CLAUDE_PROJECT_DIR as an alias for CURSOR_PROJECT_DIR, so many scripts work in both tools unchanged. If you already use Claude Code hooks, that is the fastest way to start.
How do you debug Cursor hooks?
Cursor offers two views: a Hooks tab in Customize and a Hooks output channel that shows configured and executed hooks and their errors. If a hook does not fire, check the relative path for its source first, then confirm the script is executable and prints valid JSON. Writing the raw stdin to a log file, as the docs' audit.sh example does, is the quickest way to see exactly what Cursor sends.
Keep your hook scripts with the rest of your setup
Hook scripts, rules and skills tend to get copied between projects by hand. If you keep prompts and Agent Skills as Markdown in a GitHub repo, Kipt gives you a searchable library over that repo and can install every skill into Claude Code or Codex with one npx skills add command. For Cursor itself, see the Cursor skills guide and Cursor rules guide.