Adds, lists, removes, updates, and validates hooks for coding agents (Claude Code, Codex CLI, OpenCode). Use when automating agent behavior with shell commands or plugins.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ai-driven-development:hooks-managementThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Manage hooks and automation through natural language commands.
Manage hooks and automation through natural language commands.
IMPORTANT: After adding, modifying, or removing hooks, always inform the user that they need to restart the agent for changes to take effect. Hooks are loaded at startup.
Hook Events (Claude Code, as of 2026-04 — 28 events):
Handler types: command, http, mcp_tool, prompt, agent. Some events are command-only (PostCompact, PermissionDenied, Elicitation/ElicitationResult, FileChanged, CwdChanged, ConfigChange, InstructionsLoaded, WorktreeCreate/Remove, SubagentStart, StopFailure, TeammateIdle, Setup, SessionStart, SessionEnd, Notification).
Claude Code Settings Files:
~/.claude/settings.json.claude/settings.json.claude/settings.local.json~/.claude/managed-settings.d/ (managed-settings only)Codex CLI / Codex App Settings Files (current as of 2026-06):
~/.codex/config.toml~/.codex/hooks.json or inline [hooks] tables in ~/.codex/config.toml<repo>/.codex/hooks.json or inline [hooks] tables in <repo>/.codex/config.toml (trusted projects only)config.toml.[features].hooks = false to disable them. codex_hooks is a deprecated alias./hooks; changed hook definitions are skipped until trusted.Claude Code default control mechanism for PreToolUse: emit JSON on stdout with hookSpecificOutput.permissionDecision set to "allow", "deny", "ask" (triggers the built-in user confirmation prompt), or "defer" (pause headless tool calls; resume with -p --resume). See Decision Control. Do NOT roll your own confirmation schemes (env-var flags, interactive osascript prompts, bypass tokens) — those break the built-in UX and silently fail under existing permissions.allow entries.
Codex exception: Codex PreToolUse does not support "ask" yet. In Codex configs, use deny / exit code 2 for hard blocks, additionalContext for advisory context, or Codex approval policy/permissions for native prompts.
Disable all hooks: set disableAllHooks: true in settings.json.
Parse what the user wants:
Always run validation before saving:
python3 "$SKILL_PATH/scripts/validate_hooks.py" ~/.claude/settings.json
cat ~/.claude/settings.json 2>/dev/null || echo '{}'
Use Edit tool for modifications, Write tool for new files.
| User Says | Event | Matcher | Notes |
|---|---|---|---|
| "log all bash commands" | PreToolUse | Bash | Logging to file |
| "format files after edit" | PostToolUse | Edit|Write | Run formatter |
| "block .env file changes" | PreToolUse | Edit|Write | Exit code 2 blocks |
| "notify me when done" | Notification | "" | Desktop notification |
| "run tests after code changes" | PostToolUse | Edit|Write | Filter by extension |
| "ask before dangerous commands" | PreToolUse | Bash | Claude Code: emit JSON permissionDecision: "ask" (built-in confirm UI). Codex: use approval policy if possible; hook-level ask is unsupported. |
| "require manual approval for X" | PreToolUse | Bash/Edit/Write | Claude Code: emit JSON permissionDecision: "ask", NOT exit 2. Codex: choose policy prompt or hard block. |
| "block unless confirmed" | PreToolUse | Bash | Claude Code: JSON "ask" lets the user approve per call. Codex: no hook-created confirmation prompt yet. |
{
"hooks": {
"EVENT_NAME": [
{
"matcher": "TOOL_PATTERN",
"hooks": [
{
"type": "command",
"command": "SHELL_COMMAND",
"timeout": 60
}
]
}
]
}
}
PREFER SCRIPT FILES for complex hooks. Inline commands with nested quotes, osascript, or multi-step logic often break due to JSON escaping issues.
| Complexity | Approach | Example |
|---|---|---|
| Simple | Inline | jq -r '.tool_input.command' >> log.txt |
| Medium | Inline | Single grep/jq pipe with basic conditionals |
| Complex | Script file | Dialogs, multiple conditions, osascript, error handling |
Script location: ~/.claude/hooks/ (create if needed)
Script template for PreToolUse (~/.claude/hooks/my-hook.sh) — use JSON decision control as the primary mechanism; exit codes are a fallback for simple blocking only:
#!/bin/bash
set -euo pipefail
# Read JSON input from stdin
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')
# Your logic here
if echo "$cmd" | grep -q 'pattern-requiring-confirmation'; then
# PRIMARY PATTERN for "require user confirmation": emit JSON on stdout.
# Claude Code will show its built-in confirm prompt to the user.
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "ask",
permissionDecisionReason: "Explain why this call is risky"
}
}'
exit 0
fi
if echo "$cmd" | grep -q 'pattern-to-hard-block'; then
# Hard block (no user override possible): JSON deny, NOT exit 2.
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Reason shown to Claude"
}
}'
exit 0
fi
exit 0 # Allow (silent)
Why JSON decisions, not exit 2 or home-grown prompts:
permissionDecision: "ask" triggers the built-in Claude Code confirm UI — the user sees a clean prompt and can allow/deny per-call.exit 2 is a blunt block; the user cannot override it from the UI, and Claude often re-tries with workarounds.CONFIRMED=1, osascript dialogs, bypass tokens) break the native UX, leak into command history, and are silently bypassed if the tool already has a matching permissions.allow rule.Hook config using script:
{
"type": "command",
"command": "~/.claude/hooks/my-hook.sh"
}
Other handler types (2026): http (POSTs event JSON to a URL), mcp_tool (calls a tool on a configured MCP server), prompt (evaluates a prompt with an LLM, supports $ARGUMENTS), agent (runs an agentic verifier with tools). Some events are command-only (PostCompact, PermissionDenied, Elicitation/ElicitationResult, FileChanged, CwdChanged, ConfigChange, InstructionsLoaded, WorktreeCreate/Remove, SubagentStart, StopFailure, TeammateIdle, SessionStart/End, Notification).
Always:
~/.claude/hooks/chmod +x ~/.claude/hooks/my-hook.shecho '{"tool_input":{"command":"test"}}' | ~/.claude/hooks/my-hook.shLogging (PreToolUse):
{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"
}]
}
File Protection (PreToolUse, exit 2 to block):
{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.file_path' | grep -qE '(\\.env|secrets)' && exit 2 || exit 0"
}]
}
Auto-format (PostToolUse):
{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "file=$(jq -r '.tool_input.file_path'); [[ $file == *.ts ]] && npx prettier --write \"$file\" || true"
}]
}
Desktop Notification (Notification):
{
"matcher": "",
"hooks": [{
"type": "command",
"command": "osascript -e 'display notification \"Claude needs attention\" with title \"Claude Code\"'"
}]
}
Claude Code PreToolUse hooks control tool execution by emitting JSON on stdout. This is the default mechanism — use it instead of exit codes whenever the intent is richer than "silently allow / hard block", especially when the user should be asked to confirm.
permissionDecision | Behavior | Use for |
|---|---|---|
"allow" | Bypass permissions, proceed silently | Pre-approving a safe call |
"deny" | Block, reason shown to Claude | Hard block (no user override) |
"ask" | Built-in Claude Code confirm UI shown to user | "Require manual approval for X" — the canonical pattern |
"defer" | Pause headless tool call, resume via -p --resume | External-system integrations in headless (-p) sessions |
Additional JSON fields:
permissionDecisionReason — shown to the user for "allow"/"ask", shown to Claude for "deny"updatedInput — modify tool input before executionadditionalContext — inject context for Claude before the tool executesWhen the user says anything like "require manual confirmation", "ask before doing X", "don't run Y without my approval" — this is the pattern. Do not invent bypass env vars, osascript dialogs, or confirmation tokens. The built-in prompt already handles per-call allow/deny and is the only path that integrates with existing permissions.allow rules correctly.
#!/bin/bash
set -euo pipefail
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
if echo "$cmd" | grep -qE 'supabase\s+db\s+reset'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "ask",
permissionDecisionReason: "This will destroy and recreate the local database."
}
}'
else
exit 0
fi
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
"ask" vs existing permissions.allow rulesIf the tool call already matches an entry in .claude/settings.local.json → permissions.allow (for example, "Bash" is blanket-allowed for this session), the hook's "ask" is bypassed and the call proceeds silently. Symptom: the hook appears to do nothing. Diagnose by reading .claude/settings.local.json and narrowing the allow rule, or remove the blanket allow for the matcher while the hook is in effect.
See references/claude-event-schemas.md for the full output schema.
As of June 2026, Codex hooks are enabled by default and are shared by Codex CLI, Codex IDE extension, and Codex App/desktop sessions through the same ~/.codex and trusted project .codex configuration layers.
Current Codex lifecycle events:
SessionStartSubagentStartPreToolUsePermissionRequestPostToolUsePreCompactPostCompactUserPromptSubmitSubagentStopStopDo not add the old feature flag for new configs. If hooks must be disabled, use:
[features]
hooks = false
Minimal PreToolUse blocking hook:
[[hooks.PreToolUse]]
matcher = "^Bash$"
[[hooks.PreToolUse.hooks]]
type = "command"
command = '/usr/bin/python3 ~/.codex/hooks/policy.py'
timeout = 30
statusMessage = "Checking Bash command"
Equivalent ~/.codex/hooks.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "^Bash$",
"hooks": [
{
"type": "command",
"command": "/usr/bin/python3 ~/.codex/hooks/policy.py",
"timeout": 30,
"statusMessage": "Checking Bash command"
}
]
}
]
}
}
Prefer one representation per config layer: either hooks.json or inline [hooks]. Codex loads both and warns if both exist in the same layer.
Blocking semantics: exit code 2 blocks (stderr is reason), or emit JSON {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "..."}}.
Important Codex gap: PreToolUse currently does not support permissionDecision: "ask". Returning "ask" makes the hook run fail and Codex continues the tool call. For fail-closed behavior, map Claude-style ask to Codex deny.
When adapting a Claude Code ask hook to Codex, prefer this order:
deny / exit 2 when the command must not run without review.For bash-guard specifically, Codex live mode defaults to hard deny for internal ask decisions. Only reason codes listed in BASH_GUARD_CODEX_DEFER_REASON_CODES are allowed to pass through to execpolicy, and installers only add that env var when the user passes --codex-native-prompts. Each deferred reason code must have a matching prefix_rule(... decision="prompt"); otherwise the risky command would be silently allowed.
PermissionRequest only fires when Codex is already about to ask for approval. It can return allow, deny, or no decision; it cannot create a prompt for commands that Codex would otherwise run without asking.
Codex PreToolUse can intercept Bash, apply_patch file edits, and MCP tool calls, but it is not a complete enforcement boundary: interception of richer shell paths is incomplete, and WebSearch / non-shell / non-MCP tool calls are out of scope.
See references/codex-hooks.md for full Codex hooks reference, all event input/output schemas, common patterns, and migration from the legacy AfterAgent / AfterToolUse events.
OpenCode (anomalyco/opencode v1.14.x) does NOT use config-based shell hooks. Hooks are TypeScript/JavaScript plugins that subscribe to lifecycle events. The closest analogue to PreToolUse is tool.execute.before — throwing inside it blocks the tool call.
// .opencode/plugins/env-protection.ts
import type { Plugin } from "@opencode-ai/plugin"
export default (async () => ({
tool: {
execute: {
before: async (input, output) => {
if (output.args.filePath?.includes(".env")) {
throw new Error("Reading .env is forbidden")
}
},
},
},
})) satisfies Plugin
Plugin locations:
.opencode/plugins/*.ts~/.config/opencode/plugins/*.tsopencode.json under plugin: []Common events: tool.execute.before, tool.execute.after, session.idle, session.created, file.edited, permission.asked, command.executed (~25 total).
Critical caveat (v1.14.x): tool.execute.* hooks do NOT fire for MCP tool calls — use the permission block in opencode.json to control MCP tool access instead.
For "ask before" semantics, prefer permission rules over plugin throws — they integrate with the built-in confirm UI:
{ "permission": { "bash": { "rm -rf *": "ask" } } }
See references/opencode-hooks.md for the full event catalog, migration patterns from Claude Code hooks, and npm plugin distribution.
See references/claude-event-schemas.md for complete JSON input schemas for each event type (Claude Code).
Run validation script to check hooks:
python3 "$SKILL_PATH/scripts/validate_hooks.py" <settings-file>
Validates:
| Code | Meaning | Use Case |
|---|---|---|
| 0 | Success/Allow | Continue execution |
| 2 | Block | Simple blocking (prefer JSON decision control for PreToolUse) |
| Other | Error | Log to stderr, shown in verbose mode |
Before adding hooks, verify:
* when possible)Hook not triggering: Check matcher case-sensitivity, ensure event name is exact.
Command failing: Test command standalone with sample JSON input.
Permission denied: Ensure script is executable (chmod +x).
Timeout: Increase timeout field or optimize command.
npx claudepluginhub codealive-ai/ai-driven-development --plugin ai-driven-developmentConfigures Claude Code hooks for lifecycle events like PreToolUse, SessionStart, and automation use cases such as formatting enforcement and permission control.
Discovers and installs automation hooks for Claude Code and Opencode, including security hooks, git reminders, and linting triggers.
Guides creation of Claude Code plugin hooks for event-driven automation, including prompt-based and command hooks to validate tool use, enforce policies, and integrate external tools.