Harness Notes

JSON

Hooks

5 records · Updated 2026-10-14

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.

MomentCodex CLIHook commandsClaude CodeHook commandsCursor AgentHook commandsOpenCode 2.xPlugin hooks and eventspiExtension event handlers
Hook types
  • command
  • mcp_tool
  • prompt (accepted but skipped)
  • agent (accepted but skipped)
  • command
  • http
  • mcp_tool
  • prompt
  • agent (experimental)
  • command
  • prompt
Plugin hooks and eventsExtension event handlers
Session and workspace
Project trust is decided————
project_trust

decide

The first handler answering yes or no wins; only user-level and -e extensions can answer.

Session setup—
Setup

set 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
SessionStart

add context · stop

SessionStart

add context · set environment

Only command and mcp_tool handlers run, and mcp_tool is skipped at launch.

sessionStart

add context · set environment

Fire-and-forget; continue:false does not block session creation.

session.created

in source, not in docs

observe only

session_start

observe only

Resources are discovered————
resources_discover

load extra resources

Returns extra skill, prompt and theme paths, accumulated across handlers.

Workspace opens——
workspaceOpen

load extra resources

Returns pluginPaths to load; fires on every workspace folder change.

——
Directory is added—
DirectoryAdded

add 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—
CwdChanged

set environment

No matcher. Variables written to CLAUDE_ENV_FILE are cleared at the next CwdChanged.

———
Configuration changes—
ConfigChange

block

Changes to policy settings cannot be blocked.

—
config.updated

in source, not in docs

observe only

—
Worktree is created—
WorktreeCreate

delegate

Provider operation: the hook creates the worktree and returns its path (command hooks on stdout, HTTP hooks in hookSpecificOutput.worktreePath).

———
Worktree is resolved———
worktree.resolved

in source, not in docs

observe only

—
Worktree updates———
worktree.updated

in source, not in docs

observe only

—
Worktree is removed—
WorktreeRemove

delegate

Provider operation: the hook performs removal; this is not a notification hook.

Fires only for worktrees a WorktreeCreate hook created.

———
Session details change———
session.title

modify · replace

Setting result supplies the title.

session.renamed

in source, not in docs

observe only

session.metadata.updated

in source, not in docs

observe only

Session status changes———
session.status

in source, not in docs

observe only

—
Session is viewed———
session.viewed

in source, not in docs

observe only

—
Session is moved———
session.moved

in source, not in docs

observe only

—
Session goes idle———
session.idle

in source, not in docs

observe only

—
Before switching sessions————
Before forking a session————
session_before_fork

cancel · modify

Besides cancelling, can only skip conversation restore.

Session is forked———
session.forked

in source, not in docs

observe only

—
Before navigating the session tree————
session_before_tree

cancel · replace · modify

After navigating the session tree————
session_tree

observe only

Session is deleted———
session.deleted

in source, not in docs

observe only

—
Session ends
SessionEnd

observe only

Main thread only; advisory and always synchronous.

SessionEnd

observe only

sessionEnd

observe only

—
session_shutdown

observe only

Fires on quit, reload, new, resume and fork.

Server connects———
server.connected

in source, not in docs

observe only

—
Server shuts down———
location.shutdown

in source, not in docs

observe only

—
Prompt and turn
Before a prompt or command is expanded—
UserPromptExpansion

block · add context

———
User submits a prompt
UserPromptSubmit

block · add context

UserPromptSubmit

block · add context

beforeSubmitPrompt

block

Cursor's 2.4 changelog mentions modifying prompts, but the hooks reference documents no output field for it.

session.prompt

modify · block

Throwing stops prompt admission.

input

modify · take over

Transforms chain; handled short-circuits.

Message is queued———
session.inbox.enqueued

in source, not in docs

observe only

—
Queued message delivery changes———
session.inbox.delivery.changed

in source, not in docs

observe only

—
Queued message is delivered———
session.inbox.delivered

in source, not in docs

observe only

—
Queued message is cancelled———
session.inbox.cancelled

in source, not in docs

observe only

—
Agent is selected———
session.agent.selected

in source, not in docs

observe only

—
Before the agent starts————
before_agent_start

add context · modify · replace

Returning systemPrompt replaces the whole system prompt. Since 1.2.0 also fires for triggered extension messages.

Agent starts———
session.execution.started

in source, not in docs

observe only

agent_start

observe only

Turn starts————
turn_start

observe only

Model step starts———
session.step.started

in source, not in docs

observe only

—
Model step streams———
session.step.streamed

in source, not in docs

observe only

—
Model step ends———
session.step.ended

in source, not in docs

observe only

—
Model step fails———
session.step.failed

in source, not in docs

observe only

—
Skill is activated———
session.skill.activated

in source, not in docs

observe only

—
After the agent thinks——
afterAgentThought

observe only

——
After the agent responds——
afterAgentResponse

observe only

——
Task is created————
Task is completed—
TaskCompleted

block · stop

———
Notification is sent—
Notification

observe only

———
Teammate goes idle—
TeammateIdle

block · stop

———
Turn is retried———
session.retry

retry · decide

Overrides the retry decision for a provider failure.

session.retry.scheduled

in source, not in docs

observe only

—
Turn is interrupted
Interrupt

observe 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.

——
session.execution.interrupted

in source, not in docs

observe only

—
Turn fails—
StopFailure

observe only

—
session.execution.failed

in source, not in docs

observe only

—
Agent finishes its turn
Stop

queue follow-up · stop

decision: "block" queues a continuation prompt; continue: false from any hook takes precedence.

Stop

block · add context

stop

queue follow-up

loop_limit caps follow-ups (default 5).

—
agent_before_settle

add context · queue follow-up

The last point at which a handler can append entries and request one more provider call.

Turn ends————
turn_end

add context · queue follow-up

Can append entries and request one more provider call.

Agent ends———
session.execution.succeeded

in source, not in docs

observe only

agent_end

observe only

Agent settles————
agent_settled

observe only

Tools
Tool input starts streaming———
session.tool.input.started

in source, not in docs

observe only

—
Tool input streams———
session.tool.input.delta

in source, not in docs

observe only

—
Tool input is complete———
session.tool.input.ended

in source, not in docs

observe only

—
Before a tool runs
PreToolUse

deny · 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.

PreToolUse

allow · 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".

preToolUse

allow · deny · modify

ask is accepted by the schema but not enforced for preToolUse.

tool.execute.before

modify · block

Throwing blocks the tool.

tool_call

block · modify

Mutating event.input patches arguments without re-validation. The first block wins; a thrown error blocks the tool.

Tool execution starts———
session.tool.called

in source, not in docs

observe only

Tool execution reports progress———
session.tool.progress

in source, not in docs

observe only

Tool execution ends————
tool_execution_end

observe only

After a tool runs (any result)———
tool.execute.after

modify

Receives completed or error status.

tool_result

modify

Handlers compose; each sees earlier changes.

After a tool succeeds
PostToolUse

replace · 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.

PostToolUse

add 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.

postToolUse

add context · modify

Output can be replaced only for MCP tools.

session.tool.success

in source, not in docs

observe only

—
After a tool fails—
postToolUseFailure

add context

Also fires on timeouts and permission denials.

session.tool.failed

in source, not in docs

observe only

—
After a batch of tools—
PostToolBatch

add context · stop

———
Permissions and questions
Permission is requested
PermissionRequest

allow · deny

Any deny wins. updatedInput, updatedPermissions and interrupt fail closed.

PermissionRequest

allow · deny · modify · update permissions · stop

Exit code 2 is not honoured; deny with decision.behavior. updatedInput applies only when allowing.

—
permission.evaluate

allow · deny · ask user · annotate

An explicitly configured deny is final and does not invoke the hook.

permission.asked

in source, not in docs

observe only

—
Permission is answered———
permission.replied

in source, not in docs

observe only

—
Permission is denied—
PermissionDenied

retry

Fires only in auto mode; retry is ignored for denials without a verdict.

———
Session permissions change———
session.permissions

in source, not in docs

observe only

—
Form is shown to the user———
form.created

in source, not in docs

observe only

—
User submits a form———
form.replied

in source, not in docs

observe only

—
User cancels a form———
form.cancelled

in source, not in docs

observe only

—
Shell, terminals and files
User runs a shell command (!)————
user_bash

replace · 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——
beforeShellExecution

allow · deny · ask user

shell.create.before

modify · set environment

Can change the command, working directory, timeout, shell and environment.

—
Shell command starts———
session.shell.started

in source, not in docs

observe only

—
After a shell command——
session.shell.ended

in source, not in docs

observe only

—
Background shell is created———
shell.created

in source, not in docs

observe only

—
Background shell exits———
shell.exited

in source, not in docs

observe only

—
Background shell is removed———
shell.deleted

in source, not in docs

observe only

—
Terminal is created———
pty.created

in source, not in docs

observe only

—
Terminal updates———
pty.updated

in source, not in docs

observe only

—
Terminal exits———
pty.exited

in source, not in docs

observe only

—
Terminal is removed———
pty.deleted

in source, not in docs

observe only

—
Persistent terminal is added———
persistent-pty.added

in source, not in docs

observe only

—
Persistent terminal is removed———
persistent-pty.removed

in source, not in docs

observe only

—
Before a file is read——
beforeReadFile

allow · deny

——
Before Tab reads a file——
beforeTabFileRead

allow · deny

Tab completions only, not the agent.

——
After a file is edited——
afterFileEdit

observe only

——
After Tab edits a file——
afterTabFileEdit

observe only

Tab completions only, not the agent.

——
Watched file changes—
FileChanged

set environment

—
filesystem.changed

in source, not in docs

observe only

—
Revert is staged———
session.revert.staged

in source, not in docs

observe only

—
Revert is applied———
session.revert.committed

in source, not in docs

observe only

—
Staged revert is cleared———
session.revert.cleared

in source, not in docs

observe only

—
Git branch changes———
vcs.branch.updated

in source, not in docs

observe only

—
MCP
MCP servers are registered or removed————
mcp_servers_change

take over

Handling it makes the extension responsible for connecting registered servers.

MCP server status changes———
mcp.status.changed

in source, not in docs

observe only

—
MCP resources change———
mcp.resources.changed

in source, not in docs

observe only

—
Before an MCP tool runs——
beforeMCPExecution

allow · deny · ask user

Cursor recommends failClosed: true for security-critical MCP hooks.

——
After an MCP tool runs——
afterMCPExecution

observe only

——
MCP server asks the user—
Elicitation

take over · deny · cancel

———
User answers an MCP server—
ElicitationResult

modify · deny · cancel

———
Subagents
Subagent starts
SubagentStart

add context

SubagentStart

add context

subagentStart

allow · deny

ask is treated as deny.

——
Subagent finishes
SubagentStop

queue follow-up · stop

SubagentStop

block · add context

subagentStop

queue follow-up

The follow-up is used only when the subagent completed.

——
Context and compaction
Instructions are loaded—
InstructionsLoaded

observe only

—
session.instructions.updated

in source, not in docs

observe only

—
Before context is sent to the model———
session.context

modify

System prompt, messages, tools and options for agent requests.

context

modify

context_with_system

modify · replace

Runs after every context handler, on the full transcript including system messages.

Prompt cache warming is decided————
cache_warming_decision

decide

Returns warm or stop; the last handler that answers wins.

Before context is compacted
PreCompact

stop

continue: false ends the turn before compacting.

preCompact

observe only

Cannot block or modify compaction; can show a message to the user.

session.compaction

modify · replace

Setting result skips the model call.

session.compaction.started

in source, not in docs

observe only

session_before_compact

cancel · replace

Compaction streams———
session.compaction.delta

in source, not in docs

observe only

—
After context is compacted
PostCompact

stop

continue: false ends the turn after compacting.

PostCompact

observe only

—
session.compaction.ended

in source, not in docs

observe only

session_compact

observe only

Compaction fails———
session.compaction.failed

in source, not in docs

observe only

Model and provider
Model is selected———
session.model.selected

in source, not in docs

observe only

model_select

observe only

Before the model switches—
PreModelSwitch

allow · 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—
PostModelSwitch

add 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————
Before provider headers are sent———
session.model.request

modify

Headers and raw settings; can be scoped to one provider.

Before a provider request———
session.generate

modify · replace

session.http.request

modify · replace

session.experimental.ws.send

modify

Experimental.

aisdk.sdk

in source, not in docs

replace

Supplies the AI SDK provider instance.

aisdk.language

in source, not in docs

replace

Supplies the language model instance.

before_provider_request

replace

Each returned payload replaces the request and is passed to the next handler.

Provider streams a response————
provider_stream_event

observe only

Handlers are awaited in stream order, so a slow handler delays the stream.

After a provider response———
session.http.response

modify · replace

Token usage updates———
session.usage.updated

in source, not in docs

observe only

—
Credentials change———
credential.updated

in source, not in docs

observe only

—
Active credential switches———
credential.switched

in source, not in docs

observe only

—
Provider list changes———
provider.updated

in source, not in docs

observe only

—
Model list changes———
model.updated

in source, not in docs

observe only

—
Model catalog refreshes———
models-dev.refreshed

in source, not in docs

observe only

—
Integration changes———
integration.updated

in source, not in docs

observe only

—
Messages and interface
Message starts————
message_start

observe only

Text starts streaming———
session.text.started

in source, not in docs

observe only

—
Text streams———
session.text.delta

in source, not in docs

observe only

—
Text is complete———
session.text.ended

in source, not in docs

observe only

—
Reasoning starts———
session.reasoning.started

in source, not in docs

observe only

—
Reasoning streams———
session.reasoning.delta

in source, not in docs

observe only

—
Reasoning ends———
session.reasoning.ended

in source, not in docs

observe only

—
Message updates————
message_update

observe only

Synthetic message is added———
session.synthetic

in source, not in docs

observe only

—
Message ends————
message_end

replace

The replacement must keep the message role.

Message is displayed—
MessageDisplay

modify

displayContent changes only what is shown on screen; the transcript and what Claude sees keep the original.

———
Extension prompt opens————
ui_prompt_start

observe only

Pi starts waiting on a blocking extension prompt.

Extension prompt closes————
ui_prompt_end

observe only

Text is added to the prompt———
tui.prompt.append

in source, not in docs

observe only

—
Interface command runs———
tui.command.execute

in source, not in docs

observe only

—
Toast is shown———
tui.toast.show

in source, not in docs

observe only

—
Session is selected in the interface———
tui.session.select

in source, not in docs

observe only

—
Plugins and configuration
Plugin updates———
plugin.updated

in source, not in docs

observe only

—
Project updates———
project.updated

in source, not in docs

observe only

—
Agent list changes———
agent.updated

in source, not in docs

observe only

—
Command list changes———
command.updated

in source, not in docs

observe only

—
Skill list changes———
skill.updated

in source, not in docs

observe only

—
References change———
reference.updated

in source, not in docs

observe only

—
Web search settings change———
websearch.updated

in source, not in docs

observe only

—
Update is available———
installation.update-available

in source, not in docs

observe only

—
Installation updates———
installation.updated

in source, not in docs

observe only

—
Definitions a plugin can edit
Tool definitions———
tool.transform

in source, not in docs

modify

—
Agent definitions———
agent.transform

in source, not in docs

modify

—
Command definitions———
command.transform

in source, not in docs

modify

—
Skill definitions———
skill.transform

in source, not in docs

modify

—
MCP server definitions———
mcp.transform

in source, not in docs

modify

—
Provider definitions————
Model definitions————
Integration definitions———
integration.transform

in source, not in docs

modify

—
Reference definitions———
reference.transform

in source, not in docs

modify

—
Web search definitions———
websearch.transform

in source, not in docs

modify

—
Worktree definitions———
worktree.transform

in source, not in docs

modify

—
Version control definitions———
vcs.transform

in source, not in docs

modify

—

Notes and sources

Codex CLI
Claude Code
Cursor Agent
OpenCode 2.x
pi