If you read one line: A Cursor hook is a script that sits in the agent's loop, but only two of the six hooks can say no. beforeShellExecution and beforeMCPExecution read a permission value from your script and will block the agent when it's deny. The other four — prompt, file-read, file-edit, stop — are watchers: they can log and alert, but they cannot veto. Design around that split and hooks become a real safety layer; ignore it and you'll write a "guard" that never actually guards.
An autonomous coding agent runs shell commands, edits files, and calls tools on your behalf, at machine speed, while you look away. Cursor hooks — shipped in 1.7 and extended in 3.11 — are the seam where you get a vote. They are just scripts Cursor spawns at fixed points in the loop, talking over stdin/stdout in JSON. No SDK, no plugin. The whole system is deterministic, local, and yours.
Where hooks live#
Cursor reads hooks.json from three places, and every file that exists runs:
.cursor/hooks.json— project scope. Commit it; now your whole team inherits the same policy.~/.cursor/hooks.json— your machine./etc/cursor/hooks.json— enterprise-managed, for rules a developer shouldn't be able to switch off.
Cursor reloads the file on save, so you iterate without restarting.
The one thing that decides your design#
Six hooks, but only two are gates. This is the fact most "add guardrails to Cursor" posts skate past:
- Gating hooks — can block:
beforeShellExecution,beforeMCPExecution. These read apermissionfield from your script's stdout:allow,ask(bounce it to the human), ordeny(stop it cold). - Observe-only hooks — cannot block:
beforeSubmitPrompt,beforeReadFile,afterFileEdit,stop. Your script runs, sees the event, and can log, alert, format, or stage — but its return value does not veto anything.
So if your threat model is "the agent runs something destructive in the terminal" or "the agent calls a write-capable MCP tool," you're in luck — those are exactly the two you can stop. If it's "the agent read a file it shouldn't have," a hook can notice and alarm, but the read already happened. Treat beforeReadFile as a tripwire, not a lock.
A hooks.json that blocks a dangerous command#
Register a gate on shell execution:
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{ "command": "./.cursor/hooks/guard-shell.sh" }
],
"afterFileEdit": [
{ "command": "./.cursor/hooks/format.sh" }
]
}
}
Now the guard. Cursor pipes the pending command to the script on stdin as JSON; the script decides and answers on stdout:
#!/usr/bin/env bash
# .cursor/hooks/guard-shell.sh — deny destructive or exfiltrating commands
input=$(cat)
cmd=$(printf '%s' "$input" | jq -r '.command // empty')
# rm -rf, piping the internet into a shell, or reading raw secrets → block
if printf '%s' "$cmd" | grep -Eq 'rm[[:space:]]+-rf|curl.*\|\s*sh|cat[[:space:]]+.*\.env'; then
jq -n --arg c "$cmd" '{
permission: "deny",
agent_message: "Blocked by policy: destructive or secret-reading command. Propose a safer step.",
user_message: ("Cursor hook blocked: " + $c)
}'
exit 0
fi
# anything touching production → ask the human, do not auto-run
if printf '%s' "$cmd" | grep -Eq 'prod|deploy|drop table'; then
jq -n '{ permission: "ask" }'; exit 0
fi
jq -n '{ permission: "allow" }'
Three outcomes from one script: dangerous commands are denied with a message the agent actually reads (so it self-corrects instead of retrying), risky-but-legitimate commands escalate to you, and everything else flows. The agent_message field matters more than it looks — it turns a silent wall into feedback the model can plan around.
Observe-only hooks still earn their keep#
Don't dismiss the four watchers. afterFileEdit receives the old and new contents, so it's the natural home for auto-format-and-stage or a fast linter. stop fires when the agent finishes a turn — post a summary to Slack, kick off CI, or ping yourself. beforeSubmitPrompt is your audit trail of what was asked. beforeReadFile is the tripwire that shouts when the agent reaches for .env. None of them block, but together they give you a full flight recorder of an autonomous session — which, when something does go wrong, is the difference between a post-mortem and a shrug.
Where this fits#
Cursor 3.11's Cloud Agent Hooks push the same idea up a level, from individual tool calls to the conversation itself — prompts, responses, thinking, subagents, compaction, turn completion — which is what you want if you're piping agents into a central policy or observability service rather than guarding one laptop. But start local. A twenty-line beforeShellExecution guard, committed to .cursor/hooks.json, is the highest-leverage safety you can add to a coding agent today: it's the one place you can still say no before the command runs.
If you also run Claude Code, its hook model answers the same question with different trade-offs — more events can block, and policy lives in settings.json instead. We put the two side by side in Claude Code Hooks vs Cursor Hooks.



