Create and configure event hooks/plugins that manage agent behavior — command filtering, auditing, automation, script-based blocking of actions. Use when you need to create or configure a hook. Works across IDEs (Cursor, Claude Code, OpenCode).
64
75%
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
Fix and improve this skill with Tessl
tessl review fix ./plugins/flowai-devtools/skills/engineer-hook/SKILL.mdThis 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)ad81ae6
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.