Install any skill in seconds. Free to start, no credit card required.
Get Started Free →This skill should be used when the user asks to "create a hookify rule", "write a hook rule", "configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.
.claude/skills/anthropics-writing-hookify-rules/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 79% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 91% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 161% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 77% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 91% | 0% |
Hookify rules are markdown files with YAML frontmatter that define patterns to watch for and messages to show when those patterns match. Rules are stored in .claude/hookify.{rule-name}.local.md files.
markdown--- name: rule-identifier enabled: true event: bash|file|stop|prompt|all pattern: regex-pattern-here --- Message to show Claude when this rule triggers. Can include markdown formatting, warnings, suggestions, etc.
name (required): Unique identifier for the rule
warn-dangerous-rm, block-console-logenabled (required): Boolean to activate/deactivate
true: Rule is activefalse: Rule is disabled (won't trigger)event (required): Which hook event to trigger on
bash: Bash tool commandsfile: Edit, Write, MultiEdit toolsstop: When agent wants to stopprompt: When user submits a promptall: All eventsaction (optional): What to do when rule matches
warn: Show message but allow operation (default)block: Prevent operation (PreToolUse) or stop session (Stop events)warnpattern (simple format): Regex pattern to match
Example:
yamlevent: bash pattern: rm\s+-rf
For complex rules with multiple conditions:
markdown--- name: warn-env-file-edits enabled: true event: file conditions: - field: file_path operator: regex_match pattern: \.env$ - field: new_text operator: contains pattern: API_KEY --- You're adding an API key to a .env file. Ensure this file is in .gitignore!
Condition fields:
field: Which field to checkcommandfile_path, new_text, old_text, contentoperator: How to matchregex_match: Regex pattern matchingcontains: Substring checkequals: Exact matchnot_contains: Substring must NOT be presentstarts_with: Prefix checkends_with: Suffix checkpattern: Pattern or string to matchAll conditions must match for rule to trigger.
The markdown content after frontmatter is shown to Claude when the rule triggers.
Good messages:
Example:
markdown⚠️ **Console.log detected!** You're adding console.log to production code. **Why this matters:** - Debug logs shouldn't ship to production - Console.log can expose sensitive data - Impacts browser performance **Alternatives:** - Use a proper logging library - Remove before committing - Use conditional debug builds
Match Bash command patterns:
markdown--- event: bash pattern: sudo\s+|rm\s+-rf|chmod\s+777 --- Dangerous command detected!
Common patterns:
rm\s+-rf, dd\s+if=, mkfssudo\s+, su\s+chmod\s+777, chown\s+rootMatch Edit/Write/MultiEdit operations:
markdown--- event: file pattern: console\.log\(|eval\(|innerHTML\s*= --- Potentially problematic code pattern detected!
Match on different fields:
markdown--- event: file conditions: - field: file_path operator: regex_match pattern: \.tsx?$ - field: new_text operator: regex_match pattern: console\.log\( --- Console.log in TypeScript file!
Common patterns:
console\.log\(, debugger, print\(eval\(, innerHTML\s*=, dangerouslySetInnerHTML\.env$, credentials, \.pem$node_modules/, dist/, build/Match when agent wants to stop (completion checks):
markdown--- event: stop pattern: .* --- Before stopping, verify: - [ ] Tests were run - [ ] Build succeeded - [ ] Documentation updated
Use for:
Match user prompt content (advanced):
markdown--- event: prompt conditions: - field: user_prompt operator: contains pattern: deploy to production --- Production deployment checklist: - [ ] Tests passing? - [ ] Reviewed by team? - [ ] Monitoring ready?
Literal characters: Most characters match themselves
rm matches "rm"console.log matches "console.log"Special characters need escaping:
. (any char) → \. (literal dot)( ) → \( \) (literal parens)[ ] → \[ \] (literal brackets)Common metacharacters:
\s - whitespace (space, tab, newline)\d - digit (0-9)\w - word character (a-z, A-Z, 0-9, _). - any character+ - one or more* - zero or more? - zero or one| - ORExamples:
rm\s+-rf Matches: rm -rf, rm -rf
console\.log\( Matches: console.log(
(eval|exec)\( Matches: eval( or exec(
chmod\s+777 Matches: chmod 777, chmod 777
API_KEY\s*= Matches: API_KEY=, API_KEY =Test regex patterns before using:
bashpython3 -c "import re; print(re.search(r'your_pattern', 'test text'))"
Or use online regex testers (regex101.com with Python flavor).
Too broad:
yamlpattern: log # Matches "log", "login", "dialog", "catalog"
Better: console\.log\(|logger\.
Too specific:
yamlpattern: rm -rf /tmp # Only matches exact path
Better: rm\s+-rf
Escaping issues:
"pattern" requires double backslashes \\spattern: \s works as-isLocation: All rules in .claude/ directory Naming: .claude/hookify.{descriptive-name}.local.md Gitignore: Add .claude/*.local.md to .gitignore
Good names:
hookify.dangerous-rm.local.mdhookify.console-log.local.mdhookify.require-tests.local.mdhookify.sensitive-files.local.mdBad names:
hookify.rule1.local.md (not descriptive)hookify.md (missing .local)danger.local.md (missing hookify prefix).claude/hookify.{name}.local.md file in project root.local.md fileTemporary: Set enabled: false in frontmatter Permanent: Delete the .local.md file
See ${CLAUDE_PLUGIN_ROOT}/examples/ for complete examples:
dangerous-rm.local.md - Block dangerous rm commandsconsole-log-warning.local.md - Warn about console.logsensitive-files-warning.local.md - Warn about editing .env filesMinimum viable rule:
markdown--- name: my-rule enabled: true event: bash pattern: dangerous_command --- Warning message here
Rule with conditions:
markdown--- name: my-rule enabled: true event: file conditions: - field: file_path operator: regex_match pattern: \.ts$ - field: new_text operator: contains pattern: any --- Warning message
Event types:
bash - Bash commandsfile - File editsstop - Completion checksprompt - User inputall - All eventsField options:
commandfile_path, new_text, old_text, contentuser_promptOperators:
regex_match, contains, equals, not_contains, starts_with, ends_with| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→fail | 8,576 | 3,618 | -58% | 1 | 1 | 0% | 1,499 | 3,041 | +103% | 0 | 0 | — |
case-01 | fail→pass | 9,292 | 6,249 | -33% | 1 | 1 | 0% | 1,937 | 3,471 | +79% | 0 | 0 | — |
case-02 | fail→pass | 10,330 | 6,799 | -34% | 1 | 1 | 0% | 2,041 | 3,892 | +91% | 0 | 0 | — |
case-04 | fail→pass | 6,183 | 3,916 | -37% | 1 | 1 | 0% | 1,202 | 3,133 | +161% | 0 | 0 | — |
case-05 | fail→pass | 11,340 | 4,490 | -60% | 1 | 1 | 0% | 1,812 | 3,201 | +77% | 0 | 0 | — |
case-06 | fail→pass | 11,170 | 9,684 | -13% | 1 | 1 | 0% | 1,900 | 3,621 | +91% | 0 | 0 | — |
case-07 | pass→pass | 5,978 | 2,846 | -52% | 1 | 1 | 0% | 1,098 | 2,921 | +166% | 0 | 0 | — |
case-08 | fail→pass | 35,229 | 6,575 | -81% | 1 | 1 | 0% | 3,106 | 3,689 | +19% | 0 | 0 | — |
case-09 | fail→pass | 6,186 | 3,883 | -37% | 1 | 1 | 0% | 1,227 | 3,124 | +155% | 0 | 0 | — |
case-10 | fail→pass | 9,783 | 4,346 | -56% | 1 | 1 | 0% | 1,911 | 3,284 | +72% | 0 | 0 | — |
case-11 | fail→pass | 6,188 | 4,418 | -29% | 1 | 1 | 0% | 1,275 | 3,177 | +149% | 0 | 0 | — |
case-12 | fail→pass | 6,536 | 3,820 | -42% | 1 | 1 | 0% | 1,198 | 3,104 | +159% | 0 | 0 | — |
case-13 | fail→pass | 12,079 | 3,559 | -71% | 1 | 1 | 0% | 2,100 | 2,849 | +36% | 0 | 0 | — |
case-14 | fail→pass | 5,342 | 3,091 | -42% | 1 | 1 | 0% | 1,022 | 2,954 | +189% | 0 | 0 | — |
case-15 | fail→pass | 10,462 | 3,530 | -66% | 1 | 1 | 0% | 1,819 | 3,028 | +66% | 0 | 0 | — |
case-16 | pass→pass | 7,170 | 2,046 | -71% | 1 | 1 | 0% | 1,426 | 2,693 | +89% | 0 | 0 | — |
case-17 | fail→pass | 16,042 | 1,388 | -91% | 1 | 1 | 0% | 2,777 | 2,576 | -7% | 0 | 0 | — |
case-18 | pass→pass | 8,358 | 2,815 | -66% | 1 | 1 | 0% | 1,387 | 2,738 | +97% | 0 | 0 | — |
case-19 | fail→pass | 16,721 | 1,946 | -88% | 1 | 1 | 0% | 2,996 | 2,687 | -10% | 0 | 0 | — |
case-20 | pass→pass | 13,217 | 9,021 | -32% | 1 | 1 | 0% | 2,888 | 4,391 | +52% | 0 | 0 | — |
case-21 | pass→pass | 5,392 | 4,107 | -24% | 1 | 1 | 0% | 1,075 | 3,228 | +200% | 0 | 0 | — |
case-22 | pass→pass | 7,155 | 4,703 | -34% | 1 | 1 | 0% | 1,455 | 3,326 | +129% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +68 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.