Cursor Hooks: What You Can Run Before and After the Agent

Cursor hooks attach scripts to before/after stages of the agent loop. Per the official Hooks docs, map the timeline first (sessionStart → tool before/after → stop / sessionEnd), then ship a minimal hooks.json—or scaffold with /create-hook. No pricing.

This is not a retell of the save/commit guard post (afterFileEdit / beforeShellExecution deep dive). Focus: lifecycle before/after, hooks.json, /create-hook.

When do hooks run?

One-line answer: For Agent Chat and Cmd+K, hooks follow session start → prompt submit → tool before/after → response/thought → stop → session end. Tab and workspace-open are separate surfaces.

Agent lifecycle hooks (official):

PhaseHookRole (short)
SessionsessionStart / sessionEndInject env/context / end audit (fire-and-forget)
PromptbeforeSubmitPromptValidate or block before send (continue)
Generic toolspreToolUse / postToolUse / postToolUseFailureAll tools, before/after/failure
SubagentssubagentStart / subagentStopTask subagent create/complete
Shell & MCPbeforeShellExecution·afterShellExecution / beforeMCPExecution·afterMCPExecutionGate and audit
FilesbeforeReadFile / afterFileEditRead gate / post-edit
ContextpreCompactObserve compaction (cannot block)
OutputafterAgentResponse / afterAgentThoughtTrack replies and thinking
Loop endstopOptional followup_message to continue

Other surfaces:

  • Tab: beforeTabFileRead / afterTabFileEdit
  • App: workspaceOpen — workspace open/folder change (outside agent sessions)

Three practical lifecycle axes:

  1. Start / inject — sessionStart for env and additional_context (callers do not currently enforce blocking)
  2. Tool before/after — prefer narrow events (beforeShellExecution, etc.); use preToolUse / postToolUse when you need everything
  3. End / loop — stop / subagentStop followup_message with loop_limit (default 5)

Cloud agents load command hooks from repo .cursor/hooks.json. sessionStart/sessionEnd, MCP before/after, Tab, and workspaceOpen are unavailable or deferred in cloud; user hooks in ~/.cursor/hooks.json do not apply (no home dir on the VM).

What is a minimal hooks.json?

One-line answer: version: 1 plus a hooks map. Project: /.cursor/hooks.json (paths from repo root: .cursor/hooks/...). User: ~/.cursor/hooks.json (paths from ~/.cursor/). Fastest scaffold: /create-hook in Agent chat.

/create-hook is a built-in skill listed in official Agent Skills docs. Describe the lifecycle (“audit on sessionStart, follow-up on stop after errors”) and it writes hooks.json plus scripts. Hand-edit? Use Customize → Hooks and the Hooks output channel to confirm load/run.

Minimal project example:

{
  "version": 1,
  "hooks": {
    "sessionStart": [
      { "command": ".cursor/hooks/session-init.sh" }
    ],
    "preToolUse": [
      {
        "command": ".cursor/hooks/validate-tool.sh",
        "matcher": "Shell|Task"
      }
    ],
    "stop": [
      {
        "command": ".cursor/hooks/track-stop.sh",
        "loop_limit": 5
      }
    ]
  }
}

Scripts need execute bits (chmod +x). They speak JSON over stdin/stdout. Default type is command; "type": "prompt" exists for natural-language checks, but cloud agents run command hooks only.

Per-definition options: command (required), type, timeout, matcher, failClosed (default false = fail-open), loop_limit (stop / subagentStop).

Path rules:

  • Project hooks: cwd = project root → .cursor/hooks/format.sh
  • User hooks: cwd = ~/.cursor/ → ./hooks/format.sh

Priority (high → low): Enterprise → Team → Project → User. All matching hooks run; permission merges as deny > ask > allow.

Where do you look when a hook fails?

One-line answer: Customize Hooks tab and the Hooks output channel first, then path, permissions, exit codes, and failClosed.

Checklist:

  1. Loaded? Cursor reloads hooks.json on save. If missing: Customize → Hooks; then restart Cursor.
  2. Paths — project = root-relative .cursor/hooks/...; user = under ~/.cursor/. Putting ./hooks/... in a project looks for /hooks at the repo root.
  3. Execute bit & deps — chmod +x; ensure jq/python etc. exist in the hook environment.
  4. Exit codes — 0 success (use JSON). 2 block (same as permission: "deny"). Other non-zero = default fail-open.
  5. failClosed: true — block on crash/timeout/bad exit for security gates. Permission hooks still block on invalid JSON/schema even when failClosed is false.
  6. matcher — if the hook never fires, the regex may be too narrow (preToolUse matches tool names; beforeShellExecution matches the command string).

Cloud misses: only user hooks configured; unsupported events (sessionStart, MCP, Tab, workspaceOpen); or early read-only turns where hooks are not loaded yet.

Wrap-up

Cursor hooks are about what to hang on the agent lifecycle before/after. Start with sessionStart, tool before/after, and stop in a minimal hooks.json, or scaffold with /create-hook. On failure: Hooks tab/channel → paths → exit codes → failClosed. For save/commit guards alone, see the separate hooks guard post; for the full event list, see Hooks.

Sources