Skip to content
Skill Authoring
Skill

/writing-hooks

How to write Claude Code hooks: events, matchers, blocking vs advisory, paths.

BOOST
From plugin
nlpm
15317 skills8 agents15 commands1 hook
Install
$ npx -y skills add xiaolai/nlpm --skill writing-hooks --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/writing-hooks

Context preview

The summary Claude sees to decide when to auto-load this skill.

How to write Claude Code hooks: events, matchers, blocking vs advisory, paths.

SKILL.md

writing-hooks.SKILL.md
name: writing-hooks
description: "How to write Claude Code hooks: events, matchers, blocking vs advisory, paths."
version: 0.2.0
user-invocable: false

Writing Hooks

> Scope: covers Claude Code `hooks.json` authoring and hook script design. **Hook event vocabularies are per-tool and NOT 1:1 mappable** (nlpm design decision #4): Claude uses `PreToolUse`/`PostToolUse`/`Stop`/etc.; Codex overlaps with Claude plus `PostCompact`/`SubagentStart`; Antigravity/Gemini uses a different `Before*/After* Agent/Model/Tool` decomposition. The hook-script design principles here (idempotency, fail-open, exit codes, portable paths) transfer across tools; the event names and config locations do not. For the authoritative per-tool event tables see [[nlpm:conventions-claude]] §7, [[nlpm:conventions-codex]] §6, [[nlpm:conventions-antigravity]] §5. For plugin architecture, see [[writing-plugins]]. For rules (which are simpler but static), see [[writing-rules]].

**Notation:** `$+{CLAUDE_PLUGIN_ROOT}` in this file is Claude Code's plugin-root variable, split by a `+` so Claude Code does not replace it with a path when it loads this skill; the real token has no `+` (`nlpm:conventions-claude` §2.4).

1. Three Hook Types

| Type | What it does | When to use | Complexity | |------|-------------|-------------|------------| | `command` | Runs a shell script, reads JSON from stdin | Deterministic checks: file existence, JSON validation, regex matching | Medium | | `prompt` | Injects text into Claude's context | Advisory: reminders, context injection, style guidance | Low | | `agent` | Spawns a verification agent | Complex verification: code quality, semantic analysis, multi-file checks | High |

Type Selection Flowchart

Is the check deterministic (regex, file exists, JSON schema)?
  YES --> command hook (shell script)
  NO  --> Does it need AI judgment?
    YES --> agent hook
    NO  --> prompt hook (context injection)

Command Hook Example

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$+{CLAUDE_PLUGIN_ROOT}/scripts/check-loc.sh",
            "timeout": 10000
          }
        ]
      }
    ]
  }
}

Hook script receives JSON on stdin with tool name and parameters. It outputs JSON to stdout.

Prompt Hook Example

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Remember: this project uses Result<T, E> for error handling. Never use try/catch directly."
          }
        ]
      }
    ]
  }
}

Agent Hook Example

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "agent",
            "agent": "Verify the written file follows project conventions. Check: import order, export style, naming conventions. Report any violations."
          }
        ]
      }
    ]
  }
}

2. Blocking vs Advisory

Blocking (PreToolUse with deny)

The hook prevents the tool from executing. Use for hard quality gates.

**Script output for blocking**:

{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "File exceeds 300 LOC limit (current: 342). Extract logic before writing."
  }
}

**When to block**:

  • Tests must pass before committing
  • File exceeds size limit
  • Required field missing from config
  • Dangerous operation detected (force push, drop table)

Advisory (PostToolUse with message)

The hook adds a message to Claude's context after the action completes. Use for suggestions and reminders.

**Script output for advisory**:

{
  "hookSpecificOutput": {
    "message": "The file you just edited has no tests. Consider adding tests in __tests__/."
  }
}

**When to advise**:

  • Suggest related actions (run tests, update docs)
  • Remind about conventions
  • Surface contextual information
  • Warn about potential issues without blocking

Decision Matrix

| Situation | Block or Advise? | Rationale | |-----------|-----------------|-----------| | Test failure on commit | Block | Broken tests should never be committed | | File over LOC limit | Block | Enforce hard limit | | Missing JSDoc on export | Advise | Nice to have, not a hard requirement | | No tests for new file | Advise | Reminder, not a gate | | Force push to main | Block | Destructive, irreversible | | Large file creation (>500 lines) | Advise | Might be intentional (generated code) |

**Rule of thumb**: block only what you would reject in a code review. Advise on everything else.

3. Event Selection Guide

| Event | When it fires | Common use cases | |-------|--------------|-----------------| | `PreToolUse` | Before a tool executes | Block dangerous operations, validate inputs, check preconditions | | `PostToolUse` | After a tool succeeds | Trigger follow-up actions, lint changed files, update state | | `PostToolUseFailure` | After a tool fails | Error recovery, suggest alternatives, log failures | | `UserPromptSubmit` | When user sends a message | Context injection, session setup, mode activation | | `Stop` | When Claude stops responding | Cleanup, summary generation, state persistence | | `SessionStart` | Session begins | Environment validation, context loading, config checks |

Event Selection by Goal

| Goal | Event | Hook type | |------|-------|-----------| | Prevent bad writes | `PreToolUse` + matcher `Write\|Edit` | command | | Lint after edit | `PostToolUse` + matcher `Write\|Edit` | command | | Inject project context | `UserPromptSubmit` | prompt | | Validate environment on start | `SessionStart` | command | | Save session summary on exit | `Stop` | agent | | Recover from failed bash commands | `PostToolUseFailure` + matcher `Bash` | prompt |

4. Matcher Patterns

The `matcher` field uses regex to match tool names. It applies only to `PreToolUse`, `Po

Read more
Ships withnlpm

Natural-Language Programming Manager — score, check, fix, and test NL artifacts across Claude Code, Codex CLI, and Antigravity. Tier-aware scoring with per-tool overlays. Part of the xiaolai plugin marketplace.

Get the whole plugin

Other skills on nlpm.