Capability Matrix
Events
All five universal events are supported on all three hosts. Cursor's host-specific events normalize upward into the unified shape.
| Unified event | Claude | Cursor | Codex |
|---|---|---|---|
SessionStart | SessionStart | sessionStart | SessionStart |
PreToolUse | PreToolUse | preToolUse, beforeShellExecution, beforeMCPExecution, beforeReadFile | PreToolUse |
PostToolUse | PostToolUse | postToolUse, afterShellExecution, afterMCPExecution, afterFileEdit | PostToolUse |
UserPromptSubmit | UserPromptSubmit | beforeSubmitPrompt | UserPromptSubmit |
Stop | Stop | stop | Stop |
Cursor's specific events fold into PreToolUse / PostToolUse with a unified event.tool value:
| Cursor event | Unified event.event | Unified event.tool |
|---|---|---|
beforeShellExecution | PreToolUse | Bash |
afterShellExecution | PostToolUse | Bash |
beforeMCPExecution | PreToolUse | MCP |
afterMCPExecution | PostToolUse | MCP |
beforeReadFile | PreToolUse | Read |
afterFileEdit | PostToolUse | Edit |
Codex's apply_patch is aliased to Edit.
Response fields
The unified IHookResponse:
interface IHookResponse {
decision?: "allow" | "deny" | "ask";
reason?: string;
user_message?: string;
modified_input?: Record<string, unknown>;
additional_context?: string;
}| Field | Claude | Cursor | Codex |
|---|---|---|---|
decision: "allow" | yes | yes (permission events; continue: true on beforeSubmitPrompt) | yes |
decision: "deny" | yes (exit 2) | yes (exit 2) | yes (exit 2) |
decision: "ask" | yes | downgraded to deny on Cursor 2.4.21+ (still broken on 3.2.16; opt out via cursorAskFallback: "ask") | yes |
reason | permissionDecisionReason / reason | agent_message on permission events; dropped on beforeSubmitPrompt | permissionDecisionReason / reason |
user_message | dropped + warn | user_message on permission events | dropped + warn |
additional_context | SessionStart, PreToolUse, UserPromptSubmit, PostToolUse | sessionStart, postToolUse; dropped on beforeSubmitPrompt | SessionStart, UserPromptSubmit, PostToolUse; systemMessage on Stop |
modified_input | PreToolUse (as updatedInput); dropped + warn elsewhere | preToolUse (as updated_input); dropped on specific events | dropped + warn |
Cursor beforeSubmitPrompt
Cursor's prompt-submit event response shape is allow/deny only:
{
"continue": false,
"user_message": "secrets detected; not submitting"
}It cannot inject additional_context for the model. If your handler returns additional_context on UserPromptSubmit:
- Claude: surfaces via
hookSpecificOutput.additionalContext - Cursor: dropped; bridge logs the drop to stderr
- Codex: surfaces via
hookSpecificOutput.additionalContext
For per-prompt context injection on Cursor, move the logic to SessionStart (project-level briefing) or PostToolUse (between turns).
Cursor permission: "ask" regression
A Cursor regression that started in 2.4.21 and is still present on 3.2.16 (last verified). The visible behavior changed across the regression window:
- 2.4.21 through 2.x:
askis silently treated asdeny. The agent sees the command refused with no explanation. - 3.x (verified through 3.2.16):
askis silently treated asallow. The command runs without ever prompting the user.
Either way the hook author's intent ("ask the user before running this") is dropped on the floor. The adapter reads cursor_version from _native and downgrades decision: "ask" to "deny" with a stderr warning, because that's the safer default on both eras.
To opt out:
await run(hooks, { cursorAskFallback: "ask" });"ask" will pass through to Cursor unchanged, where it will still be silently mishandled per the table above. Use this only if you'd rather Cursor's broken behavior surface than have the bridge override it.
Pre-2.4.21 Cursor is unaffected: ask passes through and worked as expected.
Exit codes
All three hosts agree:
| Code | Meaning |
|---|---|
0 | proceed (allow / ask / no decision) |
2 | block (decision: "deny") |
1 | bridge error (parse, unknown host, handler exception) |
Host resolution
run() resolves the host in this order:
- Explicit
hostoption torun(hooks, { host }). --host <id>token inargv. Theinstallcommand writes this into every generated config.- Stdin scoring (see Host Detection).
If none resolve, run() writes a stderr error and exits 1. There is no silent default.