cursor

Cursor hooks vs rules: when to enforce and when to instruct

Cursor rules are context the model reads; Cursor hooks are scripts that run in the agent loop. Per Cursor's rules docs, "Rules provide persistent, reusable context at the prompt level." Per Cursor's hooks docs, "Hooks let you observe, control, and extend the agent loop using custom scripts," and they "can observe, block, or modify behavior." Use a rule to tell the agent how to work and why. Use a hook when a check has to run on a specific action, such as denying a shell command that matches a pattern.

Side by side

RulesHooks
What it isPersistent context at the prompt levelSpawned processes that talk JSON over stdio
When it appliesAlways Apply, Apply Intelligently, Apply to Specific Files, or Apply Manually (@-mention)On agent events such as `preToolUse`, `beforeShellExecution`, `beforeMCPExecution`, `afterFileEdit`, `stop`
What it can doInform the agentAllow, deny or (on some hooks) ask; inject context; run formatters or audits
Where it livesProject rules files`hooks.json` at project (`.cursor/hooks.json`) or user (`~/.cursor/hooks.json`) level, or via plugins; Enterprise and Team sources too

All rows are from Cursor's rules and hooks pages.

A hook that denies a command

Cursor's docs show `beforeShellExecution` with a `matcher`, which for that hook "is matched against the full command string":

json
{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [
      { "command": ".cursor/hooks/block-git.sh", "matcher": "git" }
    ]
  }
}

The script reads JSON on stdin and returns `{"permission": "deny", ...}` to block, or exits with code `2`, which the docs describe as equivalent to returning `permission: "deny"`.

How failures behave

From Cursor's hooks docs:

  • Exit code `0`: the hook's JSON output is used. For permission hooks, "invalid JSON or a response that doesn't match the hook's schema blocks the action."
  • Exit code `2`: blocks the action.
  • Other exit codes: "Hook failed, action proceeds (fail-open by default)."
  • A per-hook `failClosed: true` option makes crashes, timeouts and non-zero exits block instead. The docs call it "Useful for security-critical hooks."

When several sources define hooks, "All matching hooks from every source run," and "any `deny` wins over `ask`, and `ask` wins over `allow`, regardless of source." For `preToolUse`, `"ask"` "is accepted by the schema but not enforced" today.

Using both

They aren't alternatives. A rule explains the policy (for example, "use `gh` instead of raw `git`"), so the agent plans around it. A hook runs on the action itself. Rules and skills cover guidance; see rules vs skills and skill vs rule.

Pitfalls

  • Wrong relative paths. Project hooks run from the project root, so use `.cursor/hooks/script.sh`, not `./hooks/script.sh`.
  • Expecting `ask` on `preToolUse`. It isn't enforced there today.
  • Assuming every hook runs in cloud agents. Cursor's docs list hooks that don't, including `sessionStart`, `sessionEnd` and `beforeMCPExecution`/`afterMCPExecution`, and user-level hooks aren't available there.
  • Not deciding on failure mode. Hooks fail open by default; set `failClosed` where you want the opposite.

How to check it

Open the Hooks tab in Customize or the Hooks output channel, which Cursor's docs point to for debugging. Trigger a matching command and confirm the deny. Then make the script exit with code `1` and confirm what happens with and without `failClosed`. If a skill isn't picking up, see skill not loading.

Where the kit fits

The Operator Pack ($47) is for teams setting up rules, skills and agent guardrails in Cursor. You can also start at /free. Cheat Sheet for the skim checklist mindset.

Soft next step

Write the policy as a rule first, then add a hook only for the actions that need a check on every run. Soft ladder: /free, then the Operator Pack ($47). Related: rules vs skills.

FAQ

What is the difference between Cursor hooks and rules?+

Rules are persistent context the model reads. Hooks are custom scripts that run in the agent loop and can observe, control, or extend it, according to Cursor's docs.

Can a Cursor hook block a command?+

Yes. A hook such as `beforeShellExecution` can return a `deny` permission, and an exit code of 2 also blocks the action, per Cursor's hooks docs.

Where do Cursor hooks live?+

In a `hooks.json` file at project or user level, or provided by a plugin. All matching hooks from every source run, and a deny wins over ask and allow.

Should I use a hook or a rule for safety checks?+

Use a rule to tell the agent what to do and why. Use a hook when a check has to run on a specific action, like denying a shell command that matches a pattern.

What happens if a hook script fails?+

Cursor's docs say exit code 2 blocks the action, while other failures fail open by default, so the action proceeds. A hook can set `failClosed: true` to block on failure instead, so test failure cases before relying on one.

Do hooks work with skills and MCP tools?+

Hooks run on agent events, including `beforeMCPExecution` before MCP tool calls, per Cursor's docs. Some hooks, including `beforeMCPExecution`, don't run in cloud agents, so check Cursor's hooks page for where each one applies.

Get the free starter

Value first, then a kit when you are ready. No fake urgency. Start with Harness Desk Starter at /free.