Skip to content

Cursor Adapter

Native event names: camelCase. Reference doc: https://cursor.com/docs/hooks.

Cursor splits "before tool" into specific events (beforeShellExecution, beforeMCPExecution, beforeReadFile) and uses different response shapes for each event category.

Stdin → unified

Cursor hook_event_nameUnified
sessionStartSessionStart
beforeShellExecutionPreToolUse + tool: "Bash"
afterShellExecutionPostToolUse + tool: "Bash"
beforeMCPExecutionPreToolUse + tool: "MCP"
afterMCPExecutionPostToolUse + tool: "MCP"
beforeReadFilePreToolUse + tool: "Read"
afterFileEditPostToolUse + tool: "Edit"
beforeSubmitPromptUserPromptSubmit (preserves composer_mode, attachments in _native)
preToolUsePreToolUse (tool from tool_name)
postToolUsePostToolUse
stopStop

Other Cursor events (afterAgentResponse, afterTabFileEdit, preCompact, subagentStart / subagentStop, sessionEnd) are not supported; the adapter throws on them.

Response categories

Cursor uses five response shapes, one per event category:

Permission events: preToolUse, beforeShellExecution, beforeMCPExecution, beforeReadFile

jsonc
{
  "permission": "allow" | "deny" | "ask",
  "user_message": "shown to the human",
  "agent_message": "shown to the model",
  "updated_input": { /* preToolUse only */ }
}

Mapping:

  • decisionpermission
  • reasonagent_message
  • user_messageuser_message
  • modified_inputupdated_input (only on preToolUse; dropped + warn on the specific events)

Prompt submit: beforeSubmitPrompt

jsonc
{
  "continue": true | false,
  "user_message": "shown when blocked"
}
  • decision: "deny"continue: false
  • decision: "allow" or undefined → continue: true
  • decision: "ask"continue: false (Cursor's prompt-submit doesn't support ask)
  • user_messageuser_message
  • reason → dropped + warn (no model-facing channel here)
  • additional_context → dropped + warn (Cursor's prompt-submit can't inject context)

Context injection: sessionStart, postToolUse

jsonc
{
  "additional_context": "injected to model context"
}
  • additional_contextadditional_context
  • decision, reason, user_message, modified_input → dropped + warn

Stop

jsonc
{
  "followup_message": "continuation prompt for the agent"
}
  • reasonfollowup_message
  • additional_context, user_message, decision → dropped + warn

Observational: afterShellExecution, afterMCPExecution, afterFileEdit

No response fields accepted. All fields dropped with a single warning.

permission: "ask" regression (2.4.21 onward)

Cursor's permission: "ask" has been broken since 2.4.21 and is still broken on 3.2.16 (last verified). The visible behavior is different across the regression window, but the outcome is the same: the hook author's "ask the user" intent is silently dropped.

Cursor versionWhat Cursor does with permission: "ask"
Pre-2.4.21Honored: shows the user an Allow/Deny prompt
2.4.21 through 2.xSilently treated as deny
3.x (verified on 3.2.16)Silently treated as allow (the command runs without a prompt)

The adapter reads cursor_version from stdin and downgrades decision: "ask" to "deny" on every version >= 2.4.21, because that's the safer default in both eras. The stderr warning:

text
[agent-hooks-bridge] cursor 3.2.16: 'permission: "ask"' is not honored (silently treated as 'deny' on 2.4.21-2.x, 'allow' on 3.x; last verified on 3.2.16); downgrading to 'deny'. Pass cursorAskFallback: "ask" to opt out.

To opt out and let ask reach Cursor unchanged (it will still be silently mishandled, but the bridge stays out of the way):

ts
import { run } from "@pivanov/agent-hooks-bridge";

await run(hooks, { cursorAskFallback: "ask" });

Pre-2.4.21 Cursor is unaffected: ask passes through verbatim.

Round-trip example

Stdin from Cursor (note hook_event_name: "beforeShellExecution"):

jsonc
{
  "hook_event_name": "beforeShellExecution",
  "conversation_id": "conv-xyz",
  "cursor_version": "0.46.0",
  "command": "rm -rf /tmp/foo",
  "cwd": "/Users/x/proj",
  "sandbox": false
}

The same handler from the getting-started guide returns:

ts
{
  decision: "deny",
  reason: "rm -rf is blocked"
}

Stdout (consumed by Cursor):

jsonc
{
  "permission": "deny",
  "agent_message": "rm -rf is blocked"
}

exit code: 2

Generated config

.cursor/hooks.json:

jsonc
{
  "version": 1,
  "hooks": {
    "sessionStart": [
      {
        "command": "./.hooks/format.ts --host cursor"
      }
    ],
    "preToolUse": [
      {
        "command": "./.hooks/format.ts --host cursor"
      }
    ],
    "postToolUse": [
      {
        "command": "./.hooks/format.ts --host cursor"
      }
    ],
    "beforeSubmitPrompt": [
      {
        "command": "./.hooks/format.ts --host cursor"
      }
    ],
    "stop": [
      {
        "command": "./.hooks/format.ts --host cursor"
      }
    ]
  }
}

The install command wires preToolUse and postToolUse (the generic events), not the specific ones. Cursor fires the generic events for all tool subevents.

Not affiliated with Anthropic, Anysphere, or OpenAI. Supported by LogicStar AI.