Coding Agent Hooks, Explained
Published October 8, 2026 · by the AQ team
A coding agent hook is a script of yours that the agent harness runs automatically at a fixed point in its loop: before a tool call, after a file edit, when a turn finishes. Hooks are how teams enforce rules on an agent deterministically (run the linter after every edit, block a command that matches a pattern, send a notification when the agent needs input) instead of hoping the model remembers an instruction. As of October 2026, Claude Code, Codex CLI and Cursor all ship a first-class hooks system configured in a settings file, while OpenCode covers the same ground with code plugins rather than declarative hooks. This guide explains the shared event model, shows where each harness configures hooks, gives three copy-paste examples, and covers the one security rule that applies everywhere: hooks run with your credentials.
Why hooks exist: instructions are suggestions, hooks are guarantees
You can write "always run prettier after editing" in a CLAUDE.md or AGENTS.md file, and the model will usually comply. Usually is the problem. An instruction in context competes with everything else in context, and on a long session the model can skip it. A hook removes the choice: the harness itself executes your command every time the matching event fires, whether or not the model thinks of it. That makes hooks the right tool for three jobs:
- Enforcement. Format or lint after every edit, reject commits to protected paths, block shell commands that match a deny pattern before they run.
- Visibility. Fire a desktop or chat notification when the agent finishes a turn or waits for permission, so nobody babysits a terminal. The notification patterns guide builds entirely on this.
- Audit. Append every tool call to a log the agent cannot edit away, which is useful long after the session ends.
Hooks complement, not replace, the permission system. Permission modes set the coarse posture (what the agent may do without asking); hooks are programmable policy on top, with your logic deciding per call. The permission modes guide covers the other half.
The shared event model
The three harnesses with hooks converged on remarkably similar lifecycles. Names differ, but the shape is: session events, prompt events, tool events, and stop events.
- Before a tool call. Claude Code and Codex call it PreToolUse; Cursor splits it by kind (beforeShellExecution, beforeMCPExecution, beforeReadFile). This is the only point where a hook can prevent an action, so blocking rules live here.
- After a tool call. PostToolUse (Claude Code, Codex) or afterShellExecution and afterFileEdit (Cursor). The action already happened; this is where formatting, linting and audit logging go.
- When the user submits a prompt. UserPromptSubmit (Claude Code, Codex) or beforeSubmitPrompt (Cursor): validate or enrich input, or stop a prompt that pastes a secret.
- When a turn or session starts and ends. SessionStart, Stop, SubagentStop, and in Claude Code a dedicated Notification event that fires when the agent is waiting on you. These drive notifications and cleanup.
Claude Code's event list is the largest by a wide margin: its reference documents over thirty events as of October 2026, including compaction (PreCompact, PostCompact), permission events, and file watchers, where Codex documents around a dozen and Cursor around twenty across agent, tab and app lifecycles.
Where each harness configures hooks (October 2026)
| Harness | Where hooks live | How a hook blocks | Trust model |
|---|---|---|---|
| Claude Code | A hooks block in settings files: user (~/.claude/settings.json), project (.claude/settings.json), local, managed policy, plus plugins and skill frontmatter | Exit code 2, or JSON with a permissionDecision of allow, deny or ask | No settings-file hook runs until you accept the workspace trust dialog for the folder |
| Codex CLI | hooks.json or inline hooks tables in config.toml, at user (~/.codex/) and project (repo/.codex/) layers, plus managed requirements.toml | Exit code 2, or JSON (permissionDecision deny on PreToolUse, decision block elsewhere) | Each hook is trusted by the hash of its exact definition; new or changed hooks are skipped until re-approved via the /hooks command |
| Cursor | hooks.json at project (.cursor/hooks.json), user (~/.cursor/hooks.json), team (cloud-distributed) and enterprise (MDM) levels | Exit code 2, or JSON with permission allow, deny or ask | Project hooks run in trusted workspaces; crashes fail open unless failClosed is set |
| OpenCode | No declarative hooks. Plugins: JS or TS modules in .opencode/plugins/ (project) or ~/.config/opencode/plugins/ (global), or npm packages listed in opencode.json | A plugin's tool.execute.before handler throws an error to stop the call | Plugins are code loaded at startup; review them like any dependency |
All four verified on the vendors' own documentation in October 2026. Two details worth knowing: in Claude Code, Codex and Cursor alike, hooks from every configuration layer merge and run, so a project hook does not silence a user hook. And OpenCode genuinely has no hooks file: its plugin events (tool.execute.before, tool.execute.after, session and file events) are the equivalent surface, written as code with full access to the call's arguments, which is more powerful and more work.
Three copy-paste examples
1. Claude Code: format after every edit. In .claude/settings.json, match the Edit and Write tools on PostToolUse:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": ".claude/hooks/format.sh" }
]
}
]
}
}
The hook receives the event as JSON on stdin. In .claude/hooks/format.sh (make it executable):
#!/bin/bash
FILE=$(jq -r '.tool_input.file_path // empty')
if [ -n "$FILE" ]; then
npx prettier --write "$FILE" 2>/dev/null
fi
exit 0
2. Cursor: block risky shell commands. Cursor hooks also speak JSON over stdin and stdout. In .cursor/hooks.json:
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{ "command": ".cursor/hooks/block-rm.sh", "timeout": 10 }
]
}
}
And .cursor/hooks/block-rm.sh:
#!/bin/bash
cmd=$(jq -r '.command // empty')
case "$cmd" in
*"rm -rf"*|*"git push --force"*)
echo '{"permission": "deny", "agent_message": "Blocked by hook: use a safer command."}'
;;
*)
echo '{"permission": "allow"}'
;;
esac
exit 0
3. Codex CLI: notify when a turn finishes. Inline in ~/.codex/config.toml, using the Stop event:
[[hooks.Stop]]
[[hooks.Stop.hooks]]
type = "command"
command = "notify-send 'Codex' 'Turn finished'"
Codex will list this hook under the /hooks command and ask you to trust it before it ever runs; change the command and it is skipped until you re-approve. Swap notify-send for osascript on macOS or a chat webhook, and see the notification guide for the equivalents in other harnesses.
The security rule: hooks run with your credentials
A hook is an arbitrary command executed by the harness as you. Anthropic's own reference states it plainly: command hooks execute with your full user permissions and can modify, delete, or access any file your account can, so review and test every hook command before adding it. The same is true in every harness, and it cuts both ways:
- A hooks file in a repo is code you are agreeing to run. Clone a repository with a .claude, .codex or .cursor directory and its project hooks will fire inside your session once the workspace is trusted. This is why Claude Code gates all settings-file hooks behind the workspace trust dialog and Codex pins each hook to a hash and re-asks on any change. Treat a pull request that edits hooks.json with the same suspicion as one that edits CI.
- Hooks see sensitive data. Hook input can include file contents, prompts and command output. A logging hook that writes everything to a world-readable file is a leak you installed yourself.
- A blocking hook is a guardrail, not a boundary. OpenAI's documentation describes tool hooks as a useful guardrail rather than a complete enforcement boundary, and Cursor's hooks fail open by default when a hook crashes. Pattern-matching deny rules miss encodings and indirection. The hard limits belong to the permission system and the sandbox; limiting what a coding agent can destroy covers that layering.
Where AQ fits
AQ is the multiplayer coding harness where engineering teams run AI coding agents like Claude Code and Codex together: shared live terminals, a code editor, and app previews, in your own cloud. Hooks are harness-level configuration that travels with your repo and your CLI setup, and AQ runs agents as the real CLIs (Claude Code, Codex, Cursor Agent, Kimi, Grok, or plain shells) in persistent tmux sessions on your team's VM, so the project hooks you commit fire exactly as they do on a laptop: the formatter still runs after every edit, the deny rules still block, the session just no longer dies when the lid closes.
The team half is what hooks alone cannot give you. Teammates open the same workspace and watch the same live session, so when a hook blocks a command, the person who wrote the rule can see it happen and adjust the rule or the posture rather than reconstruct it from a log later. Each workspace gets its own isolated git worktree, so experimenting with a new PreToolUse rule cannot trample a colleague's run, and per-user CLI logins mean every hook executes under the credentials of the person actually driving. The Free plan is a personal sandbox AQ creates for you with nothing to install; the Team plan ($50 per user per month early access, standard $200, billed monthly) covers VMs you connect from your own cloud or a dedicated always-on AQ-managed VM.
Plainly: write your enforcement as hooks in the repo, whatever harness you run. AQ's job is making those same hooked sessions persistent, shared and visible.
Frequently asked questions
Do coding agent hooks run automatically when I clone a repository?
Not immediately, in the harnesses that ship hooks as of October 2026. Claude Code holds back every settings-file hook until you accept the workspace trust dialog for that folder. Codex trusts each hook by the hash of its exact definition and skips new or changed hooks until you approve them via the /hooks command. Cursor runs project hooks in trusted workspaces. The protection is real but thin: once you trust the workspace, the hooks run as you, so review a repo's hook files the way you would review its CI configuration.
Does Codex CLI support hooks like Claude Code?
Yes. As of October 2026 OpenAI's Codex documentation covers a full hooks system: events including PreToolUse, PostToolUse, PermissionRequest, UserPromptSubmit, Stop, SubagentStart, SubagentStop and SessionStart, configured in hooks.json or inline tables in config.toml at user and project layers, with managed hooks for enterprises via requirements.toml. The notable difference from Claude Code is the trust model: Codex pins each hook to its definition hash and re-asks on any change.
Can a hook stop an AI coding agent from running a dangerous command?
Yes, at the before-tool events: PreToolUse in Claude Code and Codex, beforeShellExecution in Cursor. The hook inspects the proposed command and blocks it with exit code 2 or a deny decision in its JSON output. Treat this as a guardrail, not a boundary: OpenAI's docs say exactly that about tool hooks, and Cursor hooks fail open by default if the hook itself crashes. Hard limits should come from the permission system and from isolating the agent's environment.
What is the difference between hooks and permission modes?
Permission modes are the coarse, built-in posture: whether the agent must ask before editing files or running commands. Hooks are your own code injected into the loop, deciding per event with any logic you can script, and also covering things permissions never touch, like formatting after edits or sending notifications. In practice they stack: a sensible setup picks a permission mode first, then adds hooks for the rules that are specific to your team and repo.
Does OpenCode have hooks?
Not as a declarative hooks file. As of October 2026 OpenCode's extension surface is plugins: JavaScript or TypeScript modules in .opencode/plugins/ (or npm packages listed in opencode.json) that subscribe to events like tool.execute.before, tool.execute.after, file.edited and session events. A plugin blocks a tool call by throwing an error from tool.execute.before. It is the same capability as hooks with more power and more ceremony, since you are writing code against the event payloads rather than wiring a shell command in JSON.