This skill should be used when the user asks to "create a hook rule", "write a hook", "add a hook rule", "debug a hook", "test a hook", "review hook rules", "brainstorm hooks", or needs guidance on hook rule syntax, conditions, actions, expressions, or hook development best practices for OAPS.
This skill inherits all available tools. When active, it can use any tool Claude has access to.
examples/automation-scripts.mdexamples/context-injection.mdexamples/event-specific.mdexamples/git-aware-rules.mdexamples/input-modification.mdexamples/logging-audit.mdexamples/permission-control.mdreferences/actions.mdreferences/conditions.mdreferences/configuration.mdreferences/debugging.mdreferences/events.mdreferences/expressions.mdreferences/functions.mdreferences/patterns.mdreferences/priorities.mdreferences/security.mdreferences/templates.mdreferences/testing.mdThis skill provides guidance for creating, reviewing, testing, and debugging hook rules in OAPS. Hook rules are rule-based automation that respond to Claude Code events, enabling enforcement of project standards, automated workflows, and guardrails without writing custom code.
Hook rules define automated responses to Claude Code events. When an event occurs (tool use, user prompt, session lifecycle), OAPS evaluates configured rules against the event context and executes matching actions. Rules are written in TOML and stored in hook configuration files.
The hook system operates on a simple principle: events trigger rule evaluation, conditions determine matches, and actions execute responses. This declarative approach separates policy from implementation, making rules readable, maintainable, and auditable.
Hook rules serve several purposes:
Rules are stored in .oaps/hooks.toml for project-specific rules or distributed via OAPS plugins. Multiple rule sources are merged and evaluated together, with priorities determining execution order.
A minimal hook rule blocks a dangerous bash command:
[[rules]]
id = "block-rm-rf"
events = ["pre_tool_use"]
condition = 'tool_name == "Bash" and "rm -rf" in tool_input.command'
result = "block"
actions = [{ type = "deny", message = "Destructive rm -rf commands are not allowed." }]
This rule:
pre_tool_use events (before tool execution)rm -rf in the commandA more sophisticated rule warns about file modifications without blocking:
[[rules]]
id = "warn-env-modification"
events = ["pre_tool_use"]
condition = '''
tool_name in ["Edit", "Write"] and
$matches_glob(tool_input.file_path, "**/.env*")
'''
result = "warn"
priority = "high"
description = "Warn when modifying environment files"
actions = [
{ type = "warn", message = "Modifying ${tool_input.file_path}. Ensure sensitive values are not committed." },
{ type = "log", level = "info", message = "Environment file modification: ${tool_input.file_path}" }
]
Every hook rule has these core components:
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for the rule (kebab-case recommended) |
events | list | Event types that trigger evaluation: pre_tool_use, post_tool_use, permission_request, user_prompt_submit, notification, session_start, session_end, stop, subagent_stop, pre_compact, or all |
condition | string | Expression evaluated against event context; rule matches when true |
result | string | Outcome type when rule matches: block, ok, or warn |
| Field | Type | Default | Description |
|---|---|---|---|
priority | string | medium | Evaluation order: critical, high, medium, low |
terminal | bool | false | Stop evaluating further rules if this rule matches |
enabled | bool | true | Toggle rule without removing it |
description | string | none | Human-readable explanation of the rule's purpose |
actions | list | [] | Actions to execute when rule matches |
Actions define what happens when a rule matches. Each action has a type and type-specific fields:
Permission actions (for pre_tool_use, permission_request):
deny - Block the operation; requires messageallow - Explicitly permit the operationFeedback actions (all events):
log - Write to hook log; requires level (debug/info/warning/error) and messagewarn - Add warning to system messages; requires messagesuggest - Add suggestion to system messages; requires messageinject - Add context to hook output; requires contentModification actions (for pre_tool_use, permission_request):
modify - Change tool input fields; requires field, operation, valuetransform - Execute script/Python to modify input; requires entrypoint or command/scriptExecution actions (all events):
python - Run Python function; requires entrypoint (format: module.path:function_name)shell - Run shell command; requires command or scriptActions support template substitution in message and content fields. Use ${variable} syntax to interpolate context values (e.g., ${tool_name}, ${tool_input.file_path}).
Select the appropriate workflow based on the task:
Use when exploring what hook rules to create for a project. This workflow:
Use when creating new hook rules. This workflow:
Use when auditing existing hook rules. This workflow:
Use when debugging or validating hook behavior. This workflow:
Hook events correspond to Claude Code lifecycle points:
| Event | Description | Common actions |
|---|---|---|
pre_tool_use | Before tool execution | deny, allow, modify, transform, warn |
post_tool_use | After tool execution | log, inject |
permission_request | User permission prompt | deny, allow |
user_prompt_submit | User submits prompt | deny, warn, inject |
notification | System notification shown | log |
session_start | Session begins | log, inject |
session_end | Session ends | log |
stop | User interrupts (Ctrl+C) | log |
subagent_stop | Subagent terminates | log |
pre_compact | Before memory compaction | inject |
Conditions use rule-engine syntax (Python-like expressions) evaluated against event context. Available context variables depend on the event type:
Common variables:
hook_type - Event type namesession_id - Current session identifiercwd - Working directorypermission_mode - Current permission mode (default/plan/acceptEdits/bypassPermissions)Tool-specific variables (pre_tool_use, post_tool_use, permission_request):
tool_name - Name of the tool (Bash, Edit, Write, etc.)tool_input - Tool input parameters as objecttool_output - Tool response (post_tool_use only)Prompt variables (user_prompt_submit):
prompt - User's submitted prompt textGit variables (when in git repository):
git_branch - Current branch namegit_is_dirty - Repository has uncommitted changesgit_staged_files, git_modified_files, git_untracked_files, git_conflict_files - File listsExpression functions (called with $ prefix):
$is_path_under(path, base) - Secure path containment check$file_exists(path) - Check file existence$matches_glob(path, pattern) - Glob pattern matching$env(name) - Get environment variable$is_git_repo() - Check if in git repository$session_get(key) - Get value from session store$project_get(key) - Get value from project store$is_staged(path), $is_modified(path) - Git file status$git_has_staged(pattern?), $git_has_modified(pattern?) - Pattern-based git checks$current_branch() - Get current branch nameRules evaluate in priority order: critical > high > medium > low. Within the same priority, rules evaluate in definition order.
When terminal = true, matching this rule stops further rule evaluation. Use for:
The result field determines the overall outcome when a rule matches:
deny action for definitive rejection.warn or suggest actions.allow, inject, or modification actions.Match the result to the intended behavior. A block result with no deny action logs a warning but does not prevent execution. A warn result with a deny action has undefined behavior.
The skill includes reference documents for detailed information:
| Reference | When to load |
|---|---|
events.md | Writing rules for specific event types, understanding event payloads |
expressions.md | Complex conditions, available functions, expression debugging |
actions.md | Configuring actions, understanding action types and their fields |
patterns.md | Common rule patterns, recipes for typical use cases |
troubleshooting.md | Debugging rule behavior, common mistakes, validation errors |
To load references, run:
oaps skill context hook-rule-writing --references <names...>
To begin hook rule development:
Identify the automation need - Determine what behavior to enforce, automate, or observe. Consider the event type that corresponds to the target behavior.
Draft the condition - Write an expression that matches the specific scenario. Start simple and refine based on testing. Conditions support boolean operators (and, or, not), comparisons, membership tests (in), and function calls.
Select appropriate actions - Choose actions that match the result type. For block results, use deny. For warn results, use warn or suggest. For ok results, use log, inject, or modification actions.
Set priority and terminal behavior - Place safety-critical rules at critical priority. Use terminal = true for definitive allow/deny decisions.
Test the rule - Use the test workflow to verify behavior against sample scenarios before deployment.
Iterate - Refine conditions and actions based on real-world behavior. Monitor hook logs for unexpected matches or misses.
For complex rules or unfamiliar patterns, load the relevant references before writing. The patterns.md reference provides recipes for common use cases.
Several patterns appear frequently in hook rule development:
Tool-specific rules - Match on tool_name to target specific tools:
tool_name == "Bash" and "sudo" in tool_input.command
Path-based rules - Use $matches_glob or $is_path_under for file targeting:
tool_name == "Edit" and $matches_glob(tool_input.file_path, "**/test_*.py")
Git-aware rules - Combine git functions for repository context:
$is_git_repo() and $git_has_staged("*.py") and $current_branch() == "main"
Environment-conditional rules - Check environment for deployment context:
$env("CI") == "true" or $env("OAPS_ENV") == "production"
Load the patterns.md reference for comprehensive recipes covering guardrails, automation, logging, and advanced use cases.