Which moments a hook can attach to, and what it can do there. Event names link to each harness’s documentation. “Block”, “deny” and “modify” change what the agent does; “observe only” can watch but not intervene. Where hook files live is on the Locations page.
| Moment | Codex CLIHook commands | Claude CodeHook commands | Cursor AgentHook commands | OpenCode 2.xPlugin hooks and events | piExtension event handlers |
|---|---|---|---|---|---|
| Hook types |
|
|
| Plugin hooks and events | Extension event handlers |
| Session and workspace | |||||
| Project trust is decided | — | — | — | — | project_trustdecide The first handler answering yes or no wins; only user-level and -e extensions can answer. |
| Session setup | — | Setupset environment Fires only with --init-only, or --init/--maintenance in -p mode; JSON output, including additionalContext, is discarded. Only command and mcp_tool handlers run, and mcp_tool is skipped. | — | — | — |
| Session starts | SessionStartadd context, stop | SessionStartadd context, set environment Only command and mcp_tool handlers run, and mcp_tool is skipped at launch. | sessionStartadd context, set environment Fire-and-forget; continue:false does not block session creation. | session_startobserve only | |
| Resources are discovered | — | — | — | — | resources_discoverload extra resources Returns extra skill, prompt and theme paths, accumulated across handlers. |
| Workspace opens | — | — | workspaceOpenload extra resources Returns pluginPaths to load; fires on every workspace folder change. | — | — |
| Directory is added | — | DirectoryAddedadd context For /add-dir the hook's systemMessage reaches Claude as context on the next turn; for repository-root registration it only reaches the debug log. | — | — | — |
| Working directory changes | — | CwdChangedset environment No matcher. Variables written to CLAUDE_ENV_FILE are cleared at the next CwdChanged. | — | — | — |
| Configuration changes | — | — | — | ||
| Worktree is created | — | WorktreeCreatedelegate Provider operation: the hook creates the worktree and returns its path (command hooks on stdout, HTTP hooks in hookSpecificOutput.worktreePath). | — | — | — |
| Worktree is resolved | — | — | — | — | |
| Worktree updates | — | — | — | — | |
| Worktree is removed | — | WorktreeRemovedelegate Provider operation: the hook performs removal; this is not a notification hook. Fires only for worktrees a WorktreeCreate hook created. The 2.1.301 changelog reports a fix for exit removing hook-created worktrees without confirmation despite possible unpushed commits. Other WorktreeRemove cases may still remove without asking. | — | — | — |
| Session details change | — | — | — | session_info_changedobserve only | |
| Session status changes | — | — | — | — | |
| Session is viewed | — | — | — | — | |
| Session is moved | — | — | — | — | |
| Session goes idle | — | — | — | — | |
| Before switching sessions | — | — | — | — | session_before_switchcancel |
| Before forking a session | — | — | — | — | |
| Session is forked | — | — | — | — | |
| Before navigating the session tree | — | — | — | — | session_before_treecancel, replace, modify |
| After navigating the session tree | — | — | — | — | session_treeobserve only |
| Session is deleted | — | — | — | — | |
| Session ends | SessionEndobserve only | sessionEndobserve only | — | ||
| Server connects | — | — | — | — | |
| Server shuts down | — | — | — | — | |
| Prompt and turn | |||||
| Before a prompt or command is expanded | — | UserPromptExpansionblock, add context | — | — | — |
| User submits a prompt | UserPromptSubmitblock, add context | UserPromptSubmitblock, add context | beforeSubmitPromptblock Cursor's 2.4 changelog mentions modifying prompts, but the hooks reference documents no output field for it. | ||
| Message is queued | — | — | — | — | |
| Queued message delivery changes | — | — | — | — | |
| Queued message is delivered | — | — | — | — | |
| Queued message is cancelled | — | — | — | — | |
| Agent is selected | — | — | — | — | |
| Before the agent starts | — | — | — | — | before_agent_startadd context, modify, replace Returning systemPrompt replaces the whole system prompt. Since 1.2.0 also fires for triggered extension messages. |
| Agent starts | — | — | — | agent_startobserve only | |
| Turn starts | — | — | — | — | turn_startobserve only |
| Model step starts | — | — | — | — | |
| Model step streams | — | — | — | — | |
| Model step ends | — | — | — | — | |
| Model step fails | — | — | — | — | |
| Skill is activated | — | — | — | — | |
| After the agent thinks | — | — | afterAgentThoughtobserve only | — | — |
| After the agent responds | — | — | afterAgentResponseobserve only | — | — |
| Task is created | — | TaskCreatedblock | — | — | — |
| Task is completed | — | TaskCompletedblock, stop | — | — | — |
| Notification is sent | — | Notificationobserve only | — | — | — |
| Teammate goes idle | — | TeammateIdleblock, stop | — | — | — |
| Turn is retried | — | — | — | — | |
| Turn is interrupted | Interruptobserve only Runs when you interrupt an active turn on the main thread; not for subagents. The matcher is ignored. Can only emit a system message; it cannot prevent or reverse the interruption. | — | — | — | |
| Turn fails | — | StopFailureobserve only | — | — | |
| Agent finishes its turn | Stopqueue follow-up, stop decision: "block" queues a continuation prompt; continue: false from any hook takes precedence. | Stopblock, add context | — | agent_before_settleadd context, queue follow-up The last point at which a handler can append entries and request one more provider call. | |
| Turn ends | — | — | — | — | |
| Agent ends | — | — | — | agent_endobserve only | |
| Agent settles | — | — | — | — | agent_settledobserve only |
| Tools | |||||
| Tool input starts streaming | — | — | — | — | |
| Tool input streams | — | — | — | — | |
| Tool input is complete | — | — | — | — | |
| Before a tool runs | PreToolUsedeny, modify, add context permissionDecision ask, and allow without updatedInput, fail the hook run; the tool call proceeds. Covers shell, apply_patch, MCP and local function tools, not hosted tools such as web search. | PreToolUseallow, deny, ask user, defer, modify, add context defer works only in -p mode and for a single tool call per turn. A hook that times out does not block unless the handler sets onFailure: "block". | tool_callblock, modify Mutating event.input patches arguments without re-validation. The first block wins; a thrown error blocks the tool. | ||
| Tool execution starts | — | — | — | tool_execution_startobserve only | |
| Tool execution reports progress | — | — | — | tool_execution_updateobserve only | |
| Tool execution ends | — | — | — | — | tool_execution_endobserve only |
| After a tool runs (any result) | — | — | — | ||
| After a tool succeeds | PostToolUsereplace, add context decision: "block" or continue: false replaces the tool result with the feedback and continues; side effects are not undone. Also runs after shell commands that exit non-zero. | PostToolUseadd context, modify, annotate decision: "block" does not undo the tool; it adds the reason next to the result. updatedToolOutput and updatedMCPToolOutput replace the output Claude sees. | — | ||
| After a tool fails | — | PostToolUseFailureadd context | — | ||
| After a batch of tools | — | PostToolBatchadd context, stop | — | — | — |
| Permissions and questions | |||||
| Permission is requested | PermissionRequestallow, deny Any deny wins. updatedInput, updatedPermissions and interrupt fail closed. | PermissionRequestallow, deny, modify, update permissions, stop Exit code 2 is not honoured; deny with decision.behavior. updatedInput applies only when allowing. | — | permission.evaluateallow, deny, ask user, annotate An explicitly configured deny is final and does not invoke the hook. | — |
| Permission is answered | — | — | — | — | |
| Permission is denied | — | — | — | — | |
| Session permissions change | — | — | — | — | |
| Form is shown to the user | — | — | — | — | |
| User submits a form | — | — | — | — | |
| User cancels a form | — | — | — | — | |
| Shell, terminals and files | |||||
| User runs a shell command (!) | — | — | — | — | user_bashreplace, delegate, block User ! and !! commands, not the agent's bash tool. The first handler to answer wins; a failure blocks the command. |
| Before a shell command | — | — | beforeShellExecutionallow, deny, ask user | shell.create.beforemodify, set environment Can change the command, working directory, timeout, shell and environment. | — |
| Shell command starts | — | — | — | — | |
| After a shell command | — | — | afterShellExecutionobserve only | — | |
| Background shell is created | — | — | — | — | |
| Background shell exits | — | — | — | — | |
| Background shell is removed | — | — | — | — | |
| Terminal is created | — | — | — | — | |
| Terminal updates | — | — | — | — | |
| Terminal exits | — | — | — | — | |
| Terminal is removed | — | — | — | — | |
| Persistent terminal is added | — | — | — | — | |
| Persistent terminal is removed | — | — | — | — | |
| Before a file is read | — | — | beforeReadFileallow, deny | — | — |
| Before Tab reads a file | — | — | — | — | |
| After a file is edited | — | — | afterFileEditobserve only | — | — |
| After Tab edits a file | — | — | — | — | |
| Watched file changes | — | FileChangedset environment | — | — | |
| Revert is staged | — | — | — | — | |
| Revert is applied | — | — | — | — | |
| Staged revert is cleared | — | — | — | — | |
| Git branch changes | — | — | — | — | |
| MCP | |||||
| MCP servers are registered or removed | — | — | — | — | mcp_servers_changetake over Handling it makes the extension responsible for connecting registered servers. |
| MCP server status changes | — | — | — | — | |
| MCP resources change | — | — | — | — | |
| Before an MCP tool runs | — | — | beforeMCPExecutionallow, deny, ask user Cursor recommends failClosed: true for security-critical MCP hooks. | — | — |
| After an MCP tool runs | — | — | afterMCPExecutionobserve only | — | — |
| MCP server asks the user | — | Elicitationtake over, deny, cancel | — | — | — |
| User answers an MCP server | — | ElicitationResultmodify, deny, cancel | — | — | — |
| Subagents | |||||
| Subagent starts | SubagentStartadd context | SubagentStartadd context | — | — | |
| Subagent finishes | SubagentStopqueue follow-up, stop | SubagentStopblock, add context | — | — | |
| Context and compaction | |||||
| Instructions are loaded | — | InstructionsLoadedobserve only | — | — | |
| Before context is sent to the model | — | — | — | contextmodify context_with_systemmodify, replace Runs after every context handler, on the full transcript including system messages. | |
| Prompt cache warming is decided | — | — | — | — | |
| Before context is compacted | PreCompactblock | session_before_compactcancel, replace | |||
| Compaction streams | — | — | — | — | |
| After context is compacted | PostCompactobserve only | — | session_compactobserve only | ||
| Compaction fails | — | — | — | session_compact_failedobserve only | |
| Model and provider | |||||
| Model is selected | — | — | — | model_selectobserve only | |
| Before the model switches | — | PreModelSwitchallow, deny, ask user Runs only for switches a user or client requested (/model, the model picker, /config, fast mode, SDK set_model); automatic fallback and resume restores reach PostModelSwitch only. The matcher is compared against the canonical target model name; when no canonical name resolves, every PreModelSwitch hook runs regardless of matcher. A hook timeout blocks the switch, unlike PreToolUse. Outside interactive /model, ask is treated as a refusal. | — | — | — |
| After the model switches | — | PostModelSwitchadd context Also runs for automatic fallback, opusplan plan-mode transitions, and model restoration on resume, but not for a one-turn fallback-chain substitution. Cannot block. Output is delivered with the next request; only the last switch's output is delivered when several switches precede that request. | — | — | — |
| Thinking level is selected | — | — | — | — | thinking_level_selectobserve only |
| Before provider headers are sent | — | — | — | before_provider_headersmodify | |
| Before a provider request | — | — | — | session.generatemodify, replace session.http.requestmodify, replace | before_provider_requestreplace Each returned payload replaces the request and is passed to the next handler. |
| Provider streams a response | — | — | — | — | provider_stream_eventobserve only Handlers are awaited in stream order, so a slow handler delays the stream. |
| After a provider response | — | — | — | session.http.responsemodify, replace | after_provider_responseobserve only |
| Token usage updates | — | — | — | — | |
| Credentials change | — | — | — | — | |
| Active credential switches | — | — | — | — | |
| Provider list changes | — | — | — | — | |
| Model list changes | — | — | — | — | |
| Model catalog refreshes | — | — | — | — | |
| Integration changes | — | — | — | — | |
| Messages and interface | |||||
| Message starts | — | — | — | — | message_startobserve only |
| Text starts streaming | — | — | — | — | |
| Text streams | — | — | — | — | |
| Text is complete | — | — | — | — | |
| Reasoning starts | — | — | — | — | |
| Reasoning streams | — | — | — | — | |
| Reasoning ends | — | — | — | — | |
| Message updates | — | — | — | — | message_updateobserve only |
| Synthetic message is added | — | — | — | — | |
| Message ends | — | — | — | — | |
| Message is displayed | — | MessageDisplaymodify displayContent changes only what is shown on screen; the transcript and what Claude sees keep the original. | — | — | — |
| Extension prompt opens | — | — | — | — | |
| Extension prompt closes | — | — | — | — | ui_prompt_endobserve only |
| Text is added to the prompt | — | — | — | — | |
| Interface command runs | — | — | — | — | |
| Toast is shown | — | — | — | — | |
| Session is selected in the interface | — | — | — | — | |
| Plugins and configuration | |||||
| Plugin updates | — | — | — | — | |
| Project updates | — | — | — | — | |
| Agent list changes | — | — | — | — | |
| Command list changes | — | — | — | — | |
| Skill list changes | — | — | — | — | |
| References change | — | — | — | — | |
| Web search settings change | — | — | — | — | |
| Update is available | — | — | — | — | |
| Installation updates | — | — | — | — | |
| Definitions a plugin can edit | |||||
| Tool definitions | — | — | — | — | |
| Agent definitions | — | — | — | — | |
| Command definitions | — | — | — | — | |
| Skill definitions | — | — | — | — | |
| MCP server definitions | — | — | — | — | |
| Provider definitions | — | — | — | provider.transformmodify | — |
| Model definitions | — | — | — | model.transformmodify | — |
| Integration definitions | — | — | — | — | |
| Reference definitions | — | — | — | — | |
| Web search definitions | — | — | — | — | |
| Worktree definitions | — | — | — | — | |
| Version control definitions | — | — | — | — | |
Notes and sources
Codex CLI
- Matching hooks are launched concurrently, so one hook cannot stop another from starting.
- Project hooks load only in a trusted project, and every non-managed hook must also be reviewed and trusted by its hash.
- Background (async) hooks cannot block, approve or rewrite the triggering operation.
- OpenAI describes tool hooks as a guardrail, not a complete enforcement boundary.
- Codex hooks, Documentation, checked 2026-10-15
- Codex CLI 0.162.1 hook event names, Source code, checked 2026-10-14
- Codex CLI 0.162.1 hook output parsing, Source code, checked 2026-10-14
- Codex CLI 0.162.1 hook handler discovery, Source code, checked 2026-10-14
Claude Code
- onFailure: "block" on a command or HTTP hook makes a failed or timed-out hook block the action (2.1.295 and later).
- Plugin mods are a separate JavaScript hook surface with their own events and are not recorded here.
- 2.1.302 published changes: at most 12 prompt and agent hooks for one event run at once; remaining handlers wait. This is not the subagent concurrency limit or a stated cap on command/MCP hooks.
- 2.1.304 caps skill hooks from each source (a repository, personal folder or plugin) at 1,024 hooks and 16,384 matcher characters per session; these are separate from the 12 prompt/agent-hook concurrency limit.
- 2.1.304 fixes a skill once hook removing another skill’s identical hook, preserves a PreToolUse deny reason when an SDK callback hook times out, and excludes up to 10 minutes of SessionStart command-hook runtime from the VS Code subprocess initialization timeout.
- 2.1.304 adds a transcript notice identifying the event and plugin when an earlier plugin skips a mod hook. Mod tool.check gains e.isReadOnly and decider: person; settings.read receives individual layers and can rewrite merged settings. Mods remain a separate JavaScript hook surface.
- Claude Code hooks reference, Documentation, checked 2026-10-15
- Claude Code 2.1.301 published changes, Documentation, checked 2026-10-16
- Claude Code 2.1.302 published changes; no runtime probe, Documentation, checked 2026-10-19
- Claude Code 2.1.304 published changes; no runtime probe, Documentation, checked 2026-10-21
Cursor Agent
- Permission hooks block when output is invalid JSON or does not match the schema; crashes, timeouts and other exit codes fail open unless the hook sets failClosed: true.
- When several hooks answer, deny beats ask and ask beats allow.
- Sources run in priority order enterprise, team, project, user; project hooks run only in trusted workspaces.
- Cloud agents run only command hooks and skip sessionStart, sessionEnd, workspaceOpen, MCP and Tab hooks; no hooks run during early read-only turns.
- Cursor hooks, Documentation, checked 2026-10-15
OpenCode 2.x
- @opencode/cli 2.x, checked at 2.0.28. 1.x plugins do not run in 2.x.
- Plugins register hooks per domain with ctx.<domain>.hook(name, callback); each callback receives one mutable event. Hooks run in plugin order.
- Transforms are synchronous edits to registered definitions, replayed in registration order.
- Events are read with ctx.event.subscribe() and can only be observed.
- 2.1 renames provider replay data to native across text/reasoning lifecycle, tool and step/compaction events. Tool result native shallow-merges into call native; execution state is unchanged. SessionCompactionResult.providerState becomes native. session.compacted is removed; subscribe to current compaction lifecycle events.
- 2.1 adds paginated ctx.session.list to both Effect and Promise plugin APIs.
- OpenCode v2 plugin development, Documentation, checked 2026-10-15
- OpenCode 2.0.29 session hooks, Source code, checked 2026-10-15
- OpenCode 2.0.29 event union, Source code, checked 2026-10-15
- OpenCode 2.1.0 official release notes; snapshot retained in October 21 review, Documentation, checked 2026-10-21
- OpenCode v2.1.1: packages/schema/src/session-legacy.ts, Source code, checked 2026-10-21
pi
- In-process TypeScript extension events, run in extension load order; extensions run with full user permissions.
- pi's documentation names its extension type definitions as the event reference.
- Project extensions load only after the project is trusted.
- pi 1.2.0 extension event overloads, Source code, checked 2026-10-14