All skills
Skillintermediate

Cursor IDE Hooks: Exhaustive Technical Reference

> Last updated: January 2026 > Covers: Cursor v1.7 through v2.3.x

Claude Code Knowledge Pack7/10/2026

Overview

Cursor IDE Hooks: Exhaustive Technical Reference

Last updated: January 2026 Covers: Cursor v1.7 through v2.3.x

Table of Contents

  1. Overview
  2. Configuration
  3. Hook Types
  4. Input/Output Schemas
  5. Permission System
  6. Version History and Breaking Changes
  7. Known Bugs and Regressions
  8. Platform-Specific Issues
  9. Comparison with Claude Code and Gemini
  10. Security Considerations
  11. Edge Cases and Gotchas
  12. Debugging
  13. Enterprise Features
  14. Sources

Overview

Cursor Hooks were introduced in version 1.7 (October 2025) as a mechanism to observe, control, and extend the agent loop using custom scripts. Hooks are spawned as standalone processes that communicate via JSON over stdio.

Key Characteristics:

  • Hooks are deterministic programs (unlike rules/MCP which are interpreted by the LLM)
  • Configuration via JSON files
  • Receive structured input on stdin
  • Return JSON output on stdout
  • Currently in beta - APIs may change

Sources:


Configuration

File Locations (Priority Order)

  1. Enterprise-managed global directories (MDM deployment)
  2. Project-level: <project-root>/.cursor/hooks.json (version controlled)
  3. User home: ~/.cursor/hooks.json (personal automation)

Cursor runs all hooks that apply from each location.

Basic Structure

{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [
      { "command": "./hooks/script.sh" }
    ],
    "afterFileEdit": [
      { "command": "bun run hooks/format.ts" }
    ]
  }
}

Path Resolution

  • Relative paths resolve from the hooks.json file's directory, not the project root
  • Absolute paths are supported
  • Scripts must be executable (chmod +x)

JSON Schema

Community-maintained schema available at:

https://unpkg.com/cursor-hooks@latest/schema/hooks.schema.json

Enable IntelliSense in Cursor settings:

{
  "json.schemas": [
    {
      "fileMatch": [".cursor/hooks.json"],
      "url": "https://unpkg.com/cursor-hooks/schema/hooks.schema.json"
    }
  ]
}

Source: cursor-hooks npm package


Hook Types

Agent Hooks (Cmd+K / Agent Chat)

HookTriggerCan Block?Can Message Agent?
beforeSubmitPromptBefore prompt sent to LLMYesNo (beta limitation)
beforeShellExecutionBefore shell command runsYesYes
afterShellExecutionAfter shell command completesNoNo
beforeMCPExecutionBefore MCP tool invocationYesYes
afterMCPExecutionAfter MCP tool returnsNoNo
beforeReadFileBefore file content sent to LLMYesNo
afterFileEditAfter agent edits a fileNoNo
afterAgentResponseAfter agent text responseNoNo
afterAgentThoughtAfter agent thinking/reasoningNoNo
stopWhen agent loop completesNoYes (followup)

Tab Hooks (Inline Completions)

HookTriggerCan Block?
beforeTabFileReadBefore file read for Tab completionsYes
afterTabFileEditAfter Tab applies an editNo

Important: Tab hooks fire very frequently (on cursor movement) and have performance implications, especially on Windows.


Input/Output Schemas

Common Input Fields (All Hooks)

{
  "conversation_id": "uuid",
  "generation_id": "uuid",
  "model": "claude-4-sonnet",
  "hook_event_name": "beforeShellExecution",
  "cursor_version": "2.1.46",
  "workspace_roots": ["/path/to/project"],
  "user_email": "user@example.com"
}

beforeShellExecution

Input:

{
  "command": "git status",
  "cwd": "/path/to/project"
}

Note: The cwd field may be empty string in some cases - this is a known edge case.

Output:

{
  "permission": "allow",
  "user_message": "Displayed in UI",
  "agent_message": "Sent to agent context"
}

afterShellExecution

Input:

{
  "command": "git status",
  "output": "On branch main...",
  "duration": 1234
}

Note: duration excludes user approval wait time.

beforeMCPExecution

Input:

{
  "tool_name": "github__create_issue",
  "tool_input": "{\\"title\\": \\"...\\"}",
  "url": "https://mcp-server.example.com",
  "command": "npx @mcp/server"
}

Either url or command is present depending on server configuration.

Output: Same as beforeShellExecution.

afterMCPExecution

Input:

{
  "tool_name": "github__create_issue",
  "tool_input": "{...}",
  "result_json": "{...}",
  "duration": 1234
}

beforeReadFile

Input:

{
  "file_path": "/absolute/path/to/file.ts",
  "content": "file contents..."
}

Output:

{
  "permission": "allow"
}

Important: beforeReadFile does not support user_message or agent_message.

afterFileEdit

Input:

{
  "file_path": "/absolute/path/to/file.ts",
  "edits": [
    {
      "old_string": "before",
      "new_string": "after",
      "range": {
        "start_line_number": 10,
        "start_column": 5,
        "end_line_number": 10,
        "end_column": 20
      },
      "old_line": "full line before",
      "new_line": "full line after"
    }
  ]
}

Note: This is fire-and-forget - you cannot block or communicate with agent.

beforeSubmitPrompt

Input:

{
  "prompt": "user's prompt text",
  "attachments": [
    {
      "type": "file",
      "filePath": "/path/to/attachment.ts"
    },
    {
      "type": "rule",
      "filePath": "/path/to/.cursorrules"
    }
  ]
}

Output:

{
  "continue": true,
  "user_message": "Shown if blocked"
}

Beta limitation: Output JSON is currently not fully respected.

afterAgentResponse / afterAgentThought

Input:

{
  "text": "agent response or thinking text",
  "duration_ms": 5000
}

stop

Input:

{
  "status": "completed",
  "loop_count": 5
}

Status values: "completed", "aborted", "error"

Output:

{
  "followup_message": "Auto-submitted prompt"
}

Limit: Maximum 5 automatic follow-ups to prevent infinite loops.


Permission System

Permission Values

ValueBehavior
allowExecute without user intervention
denyBlock execution, show user_message
askPrompt user for confirmation

Response Fields

FieldDescriptionSupported Hooks
permissionallow/deny/askbeforeShellExecution, beforeMCPExecution
user_messageDisplayed in Cursor UIbeforeShellExecution, beforeMCPExecution
agent_messageInjected into agent contextbeforeShellExecution, beforeMCPExecution
continueBoolean to proceedbeforeSubmitPrompt
followup_messageAuto-submitted messagestop

Version History and Breaking Changes

v1.7 (October 2025)

  • Initial release of hooks system
  • Introduced: beforeShellExecution, beforeMCPExecution, beforeReadFile, afterFileEdit, stop
  • Response fields: userMessage, agentMessage (camelCase)

v2.0.x (November 2025)

  • Added: beforeSubmitPrompt, afterAgentResponse, afterAgentThought
  • Added: Tab hooks (beforeTabFileRead, afterTabFileEdit)
  • Breaking change: Field names changed to snake_case (user_message, agent_message)
  • Sandbox mode introduced

v2.0.64

  • Regression: user_message and agent_message completely non-functional
  • Workaround: Downgrade to v1.7

v2.1.x (December 2025)

  • v2.1.6: agent_message still broken; followup_message works
  • v2.1.25: Windows hooks stopped working
  • v2.1.46: Windows hooks still broken

v2.3.x (January 2026)

  • Windows Git Bash / PowerShell injection bug persists
  • CLI still has partial hook support (only shell hooks)

Sources:


Known Bugs and Regressions

Critical: Field Name Case Sensitivity

Issue: Documentation specifies snake_case but v1.7 used camelCase.

Solution: Always use snake_case in v2.0+:

{
  "permission": "deny",
  "user_message": "correct",
  "agent_message": "correct"
}

Not:

{
  "userMessage": "wrong in v2.0+",
  "agentMessage": "wrong in v2.0+"
}

Multiple Hooks Bug

Issue: When multiple hooks are defined in the same trigger array, only the first executes.

{
  "hooks": {
    "beforeShellExecution": [
      { "command": "./hook1.sh" },
      { "command": "./hook2.sh" }
    ]
  }
}

Only hook1.sh runs.

Status: Acknowledged December 2025, closed December 30, 2025 without fix confirmed.

Source: Forum: Multiple Hooks Bug

CLI vs IDE Hook Support

Issue: Cursor CLI (cursor-agent) only supports a subset of hooks.

EnvironmentSupported Hooks
IDEAll hooks
CLIbeforeShellExecution, afterShellExecution only

Source: Forum: CLI doesn't send all events

agent_message Not Reaching Context

Issue: In v2.0.64+, the agent_message field is not injected into agent context.

Status: Known regression, no timeline for fix.


Platform-Specific Issues

Windows: General Hook Failures

Symptoms:

  • "--: line 1: 3: Bad file descriptor" error
  • Empty output in Hooks panel
  • Hooks work in terminal but fail in Cursor

Affected versions: v2.0.38, v2.1.25+, v2.1.46

Workaround: Use PowerShell scripts instead of bash:

{
  "hooks": {
    "beforeShellExecution": [
      { "command": "powershell.exe -File ./hooks/check.ps1" }
    ]
  }
}

Source: Forum: Cursor Hooks On Windows

Windows: Git Bash / PowerShell Injection

Issue: When Git Bash is set as default terminal, Cursor injects PowerShell syntax that Bash cannot parse.

Error:

"--: eval: line 1: syntax error near unexpected token '[Convert]::FromBase64String'"

Root cause: Cursor uses [Convert]::FromBase64String (PowerShell) to decode hook metadata.

Workaround: Set PowerShell as default terminal for Cursor:

{
  "terminal.integrated.defaultProfile.windows": "PowerShell"
}

Source: Forum: Project-level hooks fail on Windows with Git Bash

Windows: beforeTabFileRead Performance

Issue: beforeTabFileRead is extremely slow on Windows (1+ second vs 0.5 second on macOS), causing event queue buildup.

Workaround: Disable beforeTabFileRead if not essential:

{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [...],
    "afterFileEdit": [...]
  }
}

Source: Forum: beforeTabFileRead hook is extremely slow on Windows


Comparison with Claude Code and Gemini

Claude Code Hooks

AspectCursorClaude Code
Config Location.cursor/hooks.json.claude/settings.json
Hook Types11+ lifecycle eventsPreToolUse, PostToolUse, PermissionRequest, SessionEnd
Tool MatchersN/A (hooks apply globally)Per-tool matching (e.g., "matcher": "Bash")
Input Methodstdin JSON$CLAUDE_TOOL_INPUT env var
Exit CodesNot documentedExit 2 = deny with message
SchemaCommunity-maintainedOfficial: json.schemastore.org/claude-code-settings.json

Claude Code Example:

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "if [[ \\"$CLAUDE_TOOL_INPUT\\" == *\\"rm -rf\\"* ]]; then exit 2; fi",
        "timeout": 180
      }]
    }]
  }
}

Key Differences:

  1. Claude Code has granular tool matching; Cursor hooks apply to all tool invocations of a type
  2. Claude Code uses environment variables; Cursor uses stdin
  3. Claude Code has official JSON schema; Cursor relies on community
  4. Claude Code supports regex matchers ("Edit|MultiEdit|Write"); Cursor does not

Sources:

Gemini Code Assist

As of January 2026, Gemini Code Assist does not have a documented hooks system comparable to Cursor or Claude Code.


Security Considerations

Workspace Trust

Important: Cursor disables VS Code's Workspace Trust by default.

{
  "security.workspace.trust.enabled": tru