If you read one line: Both put a script between an autonomous coding agent and your machine, but they draw the "can block" line in different places. Cursor gives you two hard gates — the terminal and MCP calls — and a plain hooks.json that reloads on save. Claude Code lets far more of the loop veto (tool calls, prompts, stops, compaction) and keeps policy in settings.json with per-tool matchers. Cursor is the lighter guard; Claude Code is the finer-grained one.

If you run an autonomous coding agent, hooks are how you keep a hand on the wheel — scripts the tool executes at fixed points in the loop, over stdin/stdout, deterministic and local. Both Claude Code and Cursor ship them. They look similar and behave differently in the one way that matters: how much of the agent you're allowed to stop.

What each one can actually block#

This is the decision, so lead with it.

Cursor blocks at two choke points. Only beforeShellExecution and beforeMCPExecution honor a permission field in your script's stdout — allow, ask, or deny. Everything else (beforeSubmitPrompt, beforeReadFile, afterFileEdit, stop) is a watcher: it sees the event and can log or alert, but cannot veto. So Cursor's safety story is precisely "the terminal and MCP tools," which — fairly — is where most of the real damage lives.

Claude Code lets much more of the loop say no. PreToolUse can deny any tool call; UserPromptSubmit can block a prompt before the model sees it; Stop and SubagentStop can refuse to let the agent finish; PreCompact can gate context compaction; PermissionRequest can auto-answer a permission prompt. That's a wider net — you can enforce "tests must pass before you stop" or "never compact without saving state," policies Cursor's observe-only stop simply can't hold.

How you block#

Cursor reads one field from stdout:

# Cursor: deny a command
jq -n '{ permission: "deny", agent_message: "Blocked by policy." }'

Claude Code gives you two routes — an exit code or structured JSON:

# Claude Code: exit-code style
echo "rm -rf blocked" >&2
exit 2   # stderr is fed back to the agent
// Claude Code: JSON style, PreToolUse
{ "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Destructive command blocked by hook" } }

Both let you hand a reason back to the model so it self-corrects instead of blindly retrying. That feedback loop is the point of a hook, not an afterthought.

Where policy lives — and how targeted it is#

Cursor keeps hooks in standalone hooks.json files at three scopes — .cursor/hooks.json (project), ~/.cursor/hooks.json (user), /etc/cursor/hooks.json (enterprise) — and reloads on save. One beforeShellExecution hook sees every shell command; you do the filtering inside your script.

Claude Code embeds hooks inside settings.json, layered across user, project, local, and managed-policy files, and adds a matcher so a hook fires only for the tools you name — Bash, Edit|Write, or an MCP pattern like mcp__github__.. That targeting is the practical difference: in Claude Code you can attach a policy to one specific MCP server's write tools* without touching anything else. Claude Code also supports non-shell handlers — command, http, mcp_tool, prompt, and agent — so a hook can call a remote policy service or even ask a model "is this safe?" before proceeding. Cursor runs commands.

The founder call#

Neither is "more secure" in the abstract; they solve for different amounts of control.

And if you run both agents, run both hook systems — they're guarding different products, and the muscle you build writing one transfers directly to the other. The shared lesson is the one worth internalizing: an autonomous agent is only as safe as the narrowest point where you can still say no before it acts. Find that point in whichever tool you use, and put a script on it today.