Creation and configuration of event hooks/plugins to manage agent behavior, command filtering, auditing, and automation. Works across IDEs (Cursor, Claude Code, OpenCode). Use when you need to: (1) Create a new hook (e.g., for formatting or security checks), (2) Configure hooks/plugins, (3) Implement logic for blocking or modifying agent actions via scripts.
69
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
This skill helps design and implement event hooks (or plugins). Hooks allow intercepting agent actions (command execution, file read/write, tool usage) and applying rules: allow, block (with explanation), request confirmation, or modify input data.
| IDE | User Hooks | Project Hooks | Format |
|---|---|---|---|
| Cursor | ~/.cursor/hooks.json | .cursor/hooks.json | JSON config + shell scripts |
| Claude Code | ~/.claude/settings.json | .claude/settings.json.claude/settings.local.json | JSON config (4 hook types) |
| OpenCode | ~/.config/opencode/plugins/*.{js,ts} | .opencode/plugins/*.{js,ts}opencode.json plugin (npm) | JS/TS modules (event-based) |
.cursor/ directory → Cursor.claude/ directory → Claude Code.opencode/ directory or opencode.json → OpenCodeIMPORTANT: After detecting the IDE, read ONLY the corresponding reference file. Do not load all references.
| Type | Cursor | Claude Code | OpenCode |
|---|---|---|---|
| Command (shell script) | Yes | Yes | No (use plugin code) |
| Prompt (LLM-evaluated) | Yes | Yes (8 events) | No |
| HTTP (POST to URL) | No | Yes | No |
| Agent (multi-turn subagent) | No | Yes (8 events) | No |
| Programmatic (JS/TS code) | No | No | Yes |
| Cursor Event | Claude Code Event | OpenCode Equivalent |
|---|---|---|
beforeShellExecution | PreToolUse (matcher: "Bash") | tool.execute.before |
afterShellExecution | PostToolUse (matcher: "Bash") | tool.execute.after |
preToolUse | PreToolUse | tool.execute.before |
postToolUse | PostToolUse | tool.execute.after |
postToolUseFailure | PostToolUseFailure | event → tool.execute.after |
sessionStart | SessionStart | event → session.created |
sessionEnd | SessionEnd | event → session.deleted |
subagentStart | SubagentStart | — |
subagentStop | SubagentStop | — |
stop | Stop | event → session.idle |
preCompact | PreCompact | experimental.session.compacting |
afterFileEdit | PostToolUse (matcher: "Edit|Write") | event → file.edited |
beforeSubmitPrompt | UserPromptSubmit | chat.message |
beforeMCPExecution | PreToolUse (matcher: "mcp__.*") | tool.execute.before |
afterMCPExecution | PostToolUse (matcher: "mcp__.*") | tool.execute.after |
beforeReadFile | PreToolUse (matcher: "Read") | tool.execute.before |
| — | PermissionRequest | permission.ask |
| — | Notification | event → various |
| — | TeammateIdle | — |
| — | TaskCompleted | event → todo.updated |
| — | ConfigChange | — |
| — | InstructionsLoaded | — |
| — | WorktreeCreate / WorktreeRemove | — |
JSON config + shell scripts. Two types: command and prompt.
In .cursor/hooks.json:
{
"version": 1,
"hooks": {
"afterFileEdit": [{ "command": ".cursor/hooks/format.sh" }],
"beforeShellExecution": [
{ "command": ".cursor/hooks/guard.sh", "matcher": "rm " }
]
}
}{ "type": "prompt", "prompt": "Is this command safe?", "timeout": 10 }For events list, input/output schemas, env vars, matcher values: see hooks_api.md.
guard.sh:
#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.command')
if [[ "$command" == *"rm -rf"* ]]; then
echo '{"permission": "ask", "user_message": "Are you sure?", "agent_message": "rm -rf requires confirmation."}'
else
echo '{"permission": "allow"}'
fiJSON config in settings.json. Four types: command, http, prompt, agent.
In .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/guard.sh",
"timeout": 30
}
]
}
]
}
}Note the nested structure: each event maps to an array of matcher groups, each containing a hooks array.
$ARGUMENTS placeholder.Prompt/agent types only available for 8 events: PermissionRequest, PostToolUse, PostToolUseFailure, PreToolUse, Stop, SubagentStop, TaskCompleted, UserPromptSubmit.
For all 18 events, input/output schemas, env vars, exit code behavior per event: see claude_code_hooks_api.md.
guard.sh (same script works for both Cursor and Claude Code):
#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
tool_input=$(echo "$input" | jq -r '.tool_input.command // empty')
if [[ "$tool_input" == *"rm -rf"* ]]; then
echo "Blocked: rm -rf requires manual confirmation" >&2
exit 2
fi
exit 0JS/TS modules returning a Hooks object. No JSON config — all logic is code.
import type { Plugin } from "@opencode-ai/plugin"
export default (async ({ project, client, $, directory, worktree }) => {
return {
"tool.execute.before": async (input) => {
// inspect/modify tool arguments
return input
},
event: async (event) => {
// handle any system event
},
}
}) satisfies Plugin.opencode/plugins/*.tsopencode.json → "plugin": ["package-name"]For all hooks, events, tool() helper, examples: see opencode_plugins_api.md.
import type { Plugin } from "@opencode-ai/plugin"
export default (async ({ client }) => ({
"tool.execute.before": async (input) => {
if (input.tool === "bash" && input.args?.command?.includes("rm -rf")) {
client.app.log({ body: { level: "warn", message: "Blocked rm -rf" } })
return { ...input, args: { command: "echo 'Blocked: rm -rf not allowed'" } }
}
return input
},
})) satisfies Pluginassets/hook_template.sh — Bash script template for command-type hooksmatcher so hooks only fire for relevant commandsfailClosed: true for security-critical hooksclaude --debug or Ctrl+O for verbose hook logging/hooks menu available for managing hooksstop_hook_active in Stop hooks to prevent infinite loopsPermissionRequest hooks don't fire in non-interactive mode (-p).opencode/package.json (Bun auto-installs)b4ba256
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.