Skip to content
Back to skills

Hookify

ASecurity

Creates REAL hooks for Claude Code — executable scripts (stdin JSON, exit 0/1/2), registered via a plugin's hooks.json or pasted into settings.json. Use when the user wants to create a hook, a safety rule, a custom validation, a lifecycle hook.

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 24, 2026
ai-agentspythongobashdebuggingapisecurity

Works with

  • claude code
  • api

Security analysis

A93/100
  • highPerforms destructive filesystem operations

Pro shows the line behind each finding and how to fix it

Scanned September 30, 2026

npx -y skills add rusharlabs/house-party-protocol --skill hookify --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Hookify?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Hookify
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/rusharlabs-hookify/badge)](https://www.skillsdirectory.com/skills/rusharlabs-hookify)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: hookify
description: Creates REAL hooks for Claude Code — executable scripts (stdin JSON, exit 0/1/2), registered via a plugin's hooks.json or pasted into settings.json. Use when the user wants to create a hook, a safety rule, a custom validation, a lifecycle hook.
---

> **Auto-Trigger:** When the user wants to create a hook, an automatic safety rule, a custom validation, or a lifecycle hook (PreToolUse/PostToolUse/SessionStart/Stop/etc.).
> **Keywords:** "hook", "create hook", "lifecycle", "safety rule", "validation", "security rule", "pre-tool", "post-tool", "block command"
> **Priority:** HIGH
> **Tools:** Read, Write, Edit, Glob, Grep, Bash

## When NOT to Activate
- Discussions about existing hooks with no intent to create new ones, or debugging a hook that is already implemented (read the script directly, do not recreate it).
- **Creating a SKILL** (a SKILL.md triggered by keyword/context) — use `skill-writer` from this same module; skill ≠ hook (a skill is an instruction for the model to follow, a hook is code that runs automatically in the tool lifecycle).
- **Packaging hooks into a distributable plugin** — after creating the hook, use `plugin-dev` from this same module for the `.claude-plugin/plugin.json` + `hooks.json` that installs it.

## Core Purpose

Create REAL hooks: **executable scripts** (not `.md` files with frontmatter) that Claude Code invokes on lifecycle events, receiving a JSON payload via **stdin** and communicating the decision via **exit code** (+ optionally an output JSON).

## Contract

**INPUT:** JSON payload via stdin — the shape varies per event, but typically includes `tool_name`, `tool_input.{...}`, `session_id`, `hook_event_name`.

**OUTPUT:** exit code (mandatory) + optionally text on stdout/stderr or structured JSON (`hookSpecificOutput`/`decision`/`permissionDecision`, depending on the event).

**EXIT CODES** (the REAL Claude Code mechanism — not `mode: warn|block` in frontmatter):

| Exit | Meaning |
|---|---|
| 0 | ok — silent, OR prints a warning/injects context WITHOUT blocking (WARN-only mode, the default of this doctrine) |
| 1 | NON-blocking error — shows up as a warning, the flow continues |
| 2 | BLOCKING error (only takes effect in `PreToolUse`) — stderr goes back to Claude, the tool does NOT run |

**STATE IT TOUCHES:**

| Resource | Reads/Writes | Purpose |
|---|---|---|
| stdin | Reads | the event's JSON payload |
| `<plugin>/hooks/hooks.json` (plugin mode) | Writes | declarative registration, resolved via `${CLAUDE_PLUGIN_ROOT}` |
| `.claude/settings.json`/`settings.local.json` (manual mode) | Writes (pasted by the operator — human gate) | direct registration, without a plugin |

## Available events

| Event | When it fires |
|---|---|
| `SessionStart` | Session start |
| `UserPromptSubmit` | The user sends a message |
| `PreToolUse` | Before a tool runs (the only phase where exit 2 blocks) |
| `PostToolUse` | After a tool runs |
| `Stop` | End of session/response |
| `PreCompact` | Before compacting/clearing context |
| `SubagentStop` | When a subagent finishes |
| `Notification` | Notification events |

`matcher` (only relevant in `PreToolUse`/`PostToolUse`): regex/pipe of tool names, e.g. `"Bash"` or `"Edit|Write|MultiEdit"`.

## Process

### 1. Write the script (stdin → decision → exit code)
Use this module's `docs/hook-template.py` as the scaffold — it already has the `decide(payload) -> (exit_code, mensagem)` structure separated from `main()` (makes it easy to test without simulating a real process) and a `--self-test`.

### 2. Choose WARN-only (default) or BLOCK (exception)
WARN-only (always exit 0, message on stdout) is the default of this doctrine — a safety hook warns; whoever actually decides to block is the human or a dedicated layer (e.g. pre-commit). BLOCK (exit 2) is the exception: only for cases where the action is irreversible AND the detection is reliable enough to never produce a blocking false positive.

### 3. Register — PLUGIN mode (recommended, auto-wire)
```json
{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [
        { "type": "command", "command": "python \"${CLAUDE_PLUGIN_ROOT}/hooks/seu_hook.py\"", "timeout": 30 }
      ]}
    ]
  }
}
```
Save it as `<your-plugin>/hooks/hooks.json`; Claude Code loads that file on its own, so do not also list it in `.claude-plugin/plugin.json` (earlier Claude Code versions refuse a plugin that names it twice) — see the `plugin-dev` skill.

### 4. OR register — MANUAL mode (no plugin, paste directly)
Add the same structure inside the `hooks` array of `.claude/settings.json`/`settings.local.json`. **This is usually a human gate** — automated sessions have a lock against self-editing settings/hooks; paste it yourself.

### 5. Test with the real payload
```bash
echo '{"tool_name":"Bash","tool_input":{"command":"..."}}' | python seu_hook.py
```

## Live example in this module: `secret_scan_on_write.py`

A real, shipped `PreToolUse` hook (matcher `Edit|Write|MultiEdit`), WARN-only: reads `tool_input.{file_path, content, ...}` from stdin, checks whether the path matches a "persistent" glob (memory/CLAUDE.md/docs) AND the content matches a secret pattern, and warns — never blocks (always exit 0). See `hooks/secret_scan_on_write.py`.

## Executed examples

```console
$ echo '{"tool_name":"Write","tool_input":{"file_path":"CLAUDE.md","content":"api_key: \"sk-ant-abcdef1234567890ABCDEF\""}}' | python hooks/secret_scan_on_write.py
[secret_scan_on_write] WARNING: possible secret in a persistent file `CLAUDE.md` (pattern `sk-ant-a...`).
  -> do NOT commit a raw credential. Reference the `.env` (e.g. "key: stored in `.env` as FOO_API_KEY"). WARN-only — the real BLOCK belongs to the pre-commit.
```
<!-- executed: 2026-07-10 · exit=0 -->
(detected the secret and WARNED — but exit=0: WARN-only never prevents the write.)

```console
$ echo '{"tool_name":"Write","tool_input":{"file_path":"CLAUDE.md","content":"API key stored in .env as FOO_API_KEY"}}' | python hooks/secret_scan_on_write.py
```
<!-- executed: 2026-07-10 · exit=0 -->
(no output — clean content, silent hook.)

```console
$ echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | python docs/hook-template.py
BLOCKED: destructive command detected ('rm -rf /'). Confirm the intent before running it by hand.
```
<!-- executed: 2026-07-10 · exit=2 -->
(BLOCK mode — exit 2 in `PreToolUse` really does prevent the tool from running. Use sparingly.)

## Anti-patterns

- ❌ Writing a hook as an `.md` file with `trigger:`/`pattern:`/`mode:` frontmatter — that is NOT how Claude Code hooks work; hooks are executables, not declarative markdown.
- ❌ Using `mode: block` by default — a block-mode false positive locks the user up mid-work; start WARN-only, promote to block only with evidence of zero false positives.
- ❌ Hardcoding `py` as the launcher — always use `python` (cross-OS portability).
- ❌ A hook that raises an unhandled exception on a malformed payload — always degrade to exit 0 on a parsing error (see `docs/hook-template.py::main`).

## Proof

```bash
python docs/hook-template.py --self-test
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.