Kipt

Cursor hooks: hooks.json, events and examples

How Cursor hooks work: hooks.json locations, agent and Tab events, exit codes, matchers, prompt-based hooks, cloud agent support and Claude Code compatibility.

By the Kipt team · Updated 24 October 2026

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:

  • preToolUse fires for every tool (Shell, Read, Write, MCP, Task and more). It can deny the call or return updated_input to rewrite it, for example turning npm install into npm ci.
  • beforeSubmitPrompt runs after you press send and before the request goes to Cursor's backend. Return "continue": false to stop a prompt, for instance one that contains a secret.
  • stop runs when the agent loop ends. If it returns a followup_message, Cursor submits that text as your next message, which lets you build "keep going until the tests pass" loops, capped by loop_limit.
  • sessionStart is fire and forget. It can return env variables for later hooks and additional_context for 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: true for 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.

Questions

Where does Cursor look for hooks.json?

Cursor reads .cursor/hooks.json in your project and ~/.cursor/hooks.json in your home folder. Enterprise setups can also place a system-wide hooks.json (for example /etc/cursor/hooks.json on Linux), and Enterprise teams can distribute hooks from the web dashboard. All matching hooks from every source run.

How do I block a command with a Cursor hook?

Add a beforeShellExecution or preToolUse hook whose script either exits with code 2 or prints JSON with permission set to deny. You can add a user_message for yourself and an agent_message that tells the agent why it was blocked and what to do instead.

Do Cursor hooks run in cloud agents?

Yes, command-based hooks from .cursor/hooks.json in your repository run in cloud agents, and Enterprise plans also run team and enterprise hooks there. User hooks from ~/.cursor/hooks.json, Tab hooks, sessionStart, sessionEnd and the MCP execution hooks do not run in cloud agents.

Can Cursor use my Claude Code hooks?

Yes. When Include Third-Party Plugins, Skills, and Other Configs is turned on in Cursor Settings (it is on by default), Cursor loads hooks from .claude/settings.local.json, .claude/settings.json and ~/.claude/settings.json and maps Claude hook names to Cursor events.

Why is my Cursor hook not running?

Open the Hooks tab in Customize or the Hooks output channel to see which hooks loaded and any errors. The most common cause is a wrong relative path: project hooks run from the project root, so use .cursor/hooks/script.sh, while user hooks run from ~/.cursor/.

More in Cursor

All guides

Keep your prompts and skills in one place, in your own GitHub. Install every skill with one command.

Get started

Kipt is an independent product, not affiliated with or endorsed by Anthropic, OpenAI, Cursor or GitHub. Claude, ChatGPT, Codex, Cursor and GitHub are trademarks of their owners.