Cursor

Cursor hooks: hooks.json, beforeShellExecution, afterFileEdit and exit codes

How do I configure hooks in Cursor to run scripts on agent events?

Create a hooks.json file at <project>/.cursor/hooks.json or ~/.cursor/hooks.json with "version": 1 and a "hooks" object that maps event names like afterFileEdit or beforeShellExecution to commands. Each script receives JSON on stdin and can print JSON on stdout, for example {"permission": "deny"} to block a shell command. Exit code 2 also blocks the action, and Cursor reloads hooks.json automatically when you save it.

Where hooks.json lives

Cursor reads hooks from several places. From highest to lowest priority:

  • Enterprise (MDM): /Library/Application Support/Cursor/hooks.json on macOS, /etc/cursor/hooks.json on Linux and WSL, C:\ProgramData\Cursor\hooks.json on Windows.
  • Team: set in the web dashboard and synced to members (Enterprise only).
  • Project: <project-root>/.cursor/hooks.json
  • User: ~/.cursor/hooks.json

Project hooks run from the project root, so use paths like .cursor/hooks/script.sh. User hooks run from ~/.cursor/, so use paths like ./hooks/script.sh.

File format and per-hook options

The top-level version must be a positive integer. Use 1. Each event holds a list of hook entries.

.cursor/hooks.json

{
  "version": 1,
  "hooks": {
    "afterFileEdit": [{ "command": ".cursor/hooks/format.sh" }]
  }
}
  • command (required): script path or command.
  • type: "command" (default) or "prompt".
  • timeout: execution timeout in seconds.
  • matcher: regex that filters when the hook runs. Empty or "*" matches everything.
  • failClosed: when true, a crash, timeout, non-zero exit or missing output blocks the action. Default false.
  • loop_limit: per-script limit on follow-ups for stop and subagentStop hooks. Default 5, null removes the cap.

Hook events

  • Agent: sessionStart, sessionEnd, preToolUse, postToolUse, postToolUseFailure, subagentStart, subagentStop, beforeShellExecution, afterShellExecution, beforeMCPExecution, afterMCPExecution, beforeReadFile, afterFileEdit, beforeSubmitPrompt, preCompact, stop, afterAgentResponse, afterAgentThought
  • Tab: beforeTabFileRead, afterTabFileEdit
  • App lifecycle: workspaceOpen

For beforeShellExecution, the matcher is tested against the full command string. For preToolUse, it is tested against the tool type, such as Shell, Read or Write.

Input, output and exit codes

Every hook gets JSON on stdin with common fields such as conversation_id, generation_id, model, hook_event_name, cursor_version, workspace_roots, user_email and transcript_path. Each event adds its own fields.

  • beforeShellExecution input: command, cwd, sandbox. Output: permission ("allow", "deny" or "ask"), user_message, agent_message.
  • afterFileEdit input: file_path and edits (a list of old_string and new_string pairs).
  • preToolUse output: permission, user_message, agent_message, updated_input.
  • beforeSubmitPrompt output: continue, user_message.
  • stop output: followup_message.
  • Exit 0: hook succeeded, Cursor uses the JSON output.
  • Exit 2: block the action, same as returning permission "deny".
  • Any other code: hook failed and the action proceeds (fail open), unless failClosed is true.
  • For permission hooks, invalid JSON or output that does not match the schema blocks the action.

Example: block risky shell commands

.cursor/hooks.json

{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [
      { "command": ".cursor/hooks/block-risky.sh", "matcher": "rm -rf|curl|wget" }
    ]
  }
}

.cursor/hooks/block-risky.sh

#!/bin/bash
cat > /dev/null
echo '{"permission": "deny", "user_message": "Blocked by project hook", "agent_message": "This command is not allowed in this repo. Ask the user to run it."}'
exit 0

shell

chmod +x .cursor/hooks/block-risky.sh

Because the matcher only fires on matching commands, the script can simply deny. Use "ask" instead of "deny" if you want the user to approve.

Example: format files after agent edits

.cursor/hooks/format.sh

#!/bin/bash
# Requires jq and prettier
file=$(jq -r '.file_path')
npx prettier --write "$file" > /dev/null 2>&1
exit 0

Wire it to afterFileEdit as shown above and make it executable with chmod +x. Cursor watches hooks config files and reloads them on save. If a hook does not load, check the Hooks output channel for errors, or restart Cursor.

Sources

More on Cursor

Other guides

Cursor: allow all terminal commandsClaude Code: allow commands without promptsCodex CLI: approval and sandbox modesGemini CLI: YOLO mode, auto_edit and allowing specific shell commandsCursor Privacy Mode: telemetry, training and data retentionClaude Code telemetry and data retention: what is sent and how to turn it offGemini CLI: turn off usage statistics and telemetryCLAUDE.md: where it goes, how it loads, and how it works with AGENTS.mdClaude Code hooks: format on save, block risky edits, get notifiedClaude Code subagents: create one, limit its tools, and call itAdd MCP servers to Cursor with mcp.json: Keep files out of Cursor with .cursorignore: Configure Codex with config.toml: Configure Gemini CLI with settings.json: Add MCP servers to Claude Code (claude mcp add, .mcp.json, scopes): Claude Code custom slash commands and skills (SKILL.md): GitHub Copilot custom instructions: Adding MCP servers to GitHub Copilot: Claude Code settings.json: file locations, precedence, and key settingsClaude Code plugins and marketplaces: install, create, and shareGemini CLI extensions and custom commands: Configuring OpenCode with opencode.json: Cline Rules: workspace, global, and conditional rulesKiro steering files: .kiro/steering, inclusion modes, and AGENTS.mdZed agent: instruction files, MCP servers, tool permissions, and ACP agentsCursor CLI: install, headless mode, permissions and CIAider: CONVENTIONS.md, .aider.conf.yml and API keysJunie guidelines, MCP and the Action Allowlist: Amp AGENTS.md, settings.json and MCP: Configure Goose extensions, hints, recipes and permissions: Customize OpenHands with skills, AGENTS.md, setup.sh and MCP: Configure Factory Droid CLI: AGENTS.md, settings.json, autonomy, custom droids and MCPConfigure Qwen Code: settings.json, providers, QWEN.md, MCP and approval modesCursor sandbox: sandbox.json, network allowlist and run modesCursor settings: settings.json, Cursor Settings and the ~/.cursor config filesCursor browser agent: @browser, browser automation and approval modesCursor subagents: .cursor/agents, frontmatter, built-in Explore, Bash and BrowserCursor telemetry and OpenTelemetry export (Privacy Mode, OTLP, Analytics API): Cursor rules setup: .cursor/rules, .mdc frontmatter, globs, alwaysApply and AGENTS.md

Get the weekly agent stack update

New official MCP servers, spec changes and harness releases, checked against the source. One email a week, no fluff.

Reviewed Oct 9, 2026. Settings change often; the linked vendor docs are the source of truth.