Skip to content

Hooks

Hooks run custom actions at specific points in the agent lifecycle. Use them to lint files after edits, run tests after source changes, log tool usage, or enforce pre-session checks. A hook binds a handler to a lifecycle event, with an optional matcher that filters which tools or files trigger it.

interface Hook {
event: HookEvent;
matcher?: string;
handler: string;
scope: Scope;
type?: "command" | "prompt" | "agent";
timeout?: number;
statusMessage?: string;
once?: boolean;
async?: boolean;
model?: string;
cwd?: string;
env?: Record<string, string>;
}

Hooks are defined in the hooks section of .ai/config.yaml:

hooks:
- event: postToolUse
matcher: "Bash"
handler: "npx biome check --write"
scope: project
- event: sessionStart
handler: "echo 'Session started at $(date)'"
scope: user
- event: preToolUse
matcher: "Bash"
type: prompt
handler: "Review this command for safety before allowing execution."
model: haiku
scope: project

dotai defines 20 lifecycle events. Not all events are supported by every tool — see cross-tool support for the full matrix.

EventFires whenMatcher type
preToolUseBefore a tool is invokedTool name (e.g. Bash, Write)
postToolUseAfter a tool completesTool name
preFileEditBefore a file is modifiedFile glob (e.g. *.ts, src/**)
postFileEditAfter a file is modifiedFile glob
sessionStartWhen the agent session beginsNone
sessionEndWhen the agent session endsNone
userPromptSubmittedWhen the user submits a promptNone
agentStopWhen the primary agent stopsNone
subagentStopWhen a sub-agent stopsNone
errorOccurredWhen an error occursNone
permissionRequestWhen a permission check is triggeredNone
postToolUseFailureWhen a tool invocation failsTool name
notificationWhen the agent emits a notificationNone
subagentStartWhen a sub-agent startsNone
teammateIdleWhen a teammate agent becomes idleNone
taskCompletedWhen a task completesNone
configChangeWhen configuration changesNone
worktreeCreateWhen a git worktree is createdNone
worktreeRemoveWhen a git worktree is removedNone
preCompactBefore context compaction occursNone

The type field controls how the handler is executed. There are three types:

Runs the handler as a shell command. This is the default when type is omitted.

- event: postToolUse
matcher: "Write"
type: command
handler: "npx prettier --write"
scope: project

Sends the handler text as a prompt to the AI model for evaluation. Useful for review gates and safety checks.

- event: preToolUse
matcher: "Bash"
type: prompt
handler: "Review this command for safety. Reject if it modifies production data."
model: haiku
scope: project

Delegates the handler to a full agent invocation. The handler text becomes the agent’s prompt.

- event: postToolUse
matcher: "Write"
type: agent
handler: "Review the changes for security vulnerabilities and report findings."
model: sonnet
scope: project
FieldTypeRequiredDefaultDescription
eventHookEventYesLifecycle event to listen for
matcherstringNoFilter condition — tool name for tool events, file glob for file-edit events, omit for session/agent events
handlerstringYesShell command (command type) or prompt text (prompt/agent type)
scopeScopeYesScope tier (enterprise / project / user / local)
type"command" | "prompt" | "agent"No"command"Handler execution type
timeoutnumberNoTimeout in milliseconds for hook execution
statusMessagestringNoStatus message shown in the UI while the hook is running
oncebooleanNofalseWhen true, the hook fires only once per session
asyncbooleanNofalseWhen true, the hook runs asynchronously without blocking (command type only)
modelstringNoModel override for prompt/agent hook evaluation (ignored for command type)
cwdstringNoWorking directory for hook execution
envRecord<string, string>NoEnvironment variables for hook execution

Run the linter automatically whenever the agent uses the Write tool:

- event: postToolUse
matcher: "Write"
handler: "npx biome check --write"
scope: project

Use a prompt hook to review dangerous commands before execution:

- event: preToolUse
matcher: "Bash"
type: prompt
handler: "Check if this command could delete files or modify production data. Block if unsafe."
model: haiku
scope: project

Run an initialization script once when the session starts:

- event: sessionStart
handler: "scripts/setup-dev-env.sh"
once: true
statusMessage: "Setting up development environment..."
scope: project

Log tool usage without blocking the agent:

- event: postToolUse
handler: "echo '$(date): Tool used' >> .ai/agent.log"
async: true
scope: local

Hook support varies across tools. Claude Code and Copilot have the broadest support; Cursor only handles file-edit events; Codex has no hook support.

AspectClaude CodeCursorCodexCopilot
Support17 events2 eventsNone18 events
EventClaude CodeCursorCopilot
preToolUseYesNoYes
postToolUseYesNoYes
preFileEditNoYesNo
postFileEditNoYesNo
sessionStartYesNoYes
sessionEndYesNoYes
userPromptSubmittedYesNoYes
agentStopYesNoYes
subagentStopYesNoYes
errorOccurredNoNoYes
permissionRequestYesNoYes
postToolUseFailureYesNoYes
notificationYesNoYes
subagentStartYesNoYes
teammateIdleYesNoYes
taskCompletedYesNoYes
configChangeYesNoYes
worktreeCreateYesNoYes
worktreeRemoveYesNoYes
preCompactYesNoYes

Notes:

  • Claude Code supports 17 of 20 events. preFileEdit, postFileEdit, and errorOccurred are not supported. Event names are translated to PascalCase (e.g. userPromptSubmittedUserPromptSubmit, agentStopStop). Claude Code also supports the type, timeout, statusMessage, once, async, and model fields.
  • Cursor translates file-edit hooks into rule-triggered actions in .cursor/rules/. Only preFileEdit and postFileEdit map; all other events are dropped.
  • Copilot outputs hooks to .github/hooks/hooks.json. It supports 18 of 20 events — only preFileEdit and postFileEdit are not supported.
  • Codex: No hook support. All hooks are silently dropped when emitting to Codex. If your workflow depends on hooks, Codex will not fire them.
  • Cursor: Only file-edit events (preFileEdit, postFileEdit) are emitted. Tool events, session events, and agent lifecycle events are dropped.
  • Copilot: File-edit hooks (preFileEdit, postFileEdit) are not supported. The remaining 18 events are emitted to .github/hooks/hooks.json. Advanced fields (type, timeout, statusMessage, once, async, model) are not supported by Copilot — only command and matcher are emitted.
  • Hook types: Only Claude Code supports the prompt and agent hook types. Other tools treat all hooks as shell commands.