Agent skill

Writing Hooks

by xiaolai in xiaolai/nlpm

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

ISCAuto-check passedAgent Workflows

Install Writing Hooks

skills CLI
$ npx skills add xiaolai/nlpm --skill writing-hooks -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install xiaolai/nlpm writing-hooks --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/xiaolai/nlpm.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/nlpm/writing-hooks .claude/skills/writing-hooks && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
writing-hooks
GitHub stars
146
Token cost
~2.9k tokens
SKILL.md length
1,019 words
Files
1
Skills in repo
15
Repo updated
First seen
Licence
ISC

At a glance

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

  • Works in 8 steps: Three Hook Types → Blocking vs Advisory → Event Selection Guide → …
  • Tasks that involve Hooks and plugins
  • SKILL.md covers 1. Three Hook Types, 2. Blocking vs Advisory, 3. Event Selection Guide and 4. Matcher Patterns, plus 4 more sections
  • Calls jq

What it does

Writing Hooks is an agent skill from xiaolai/nlpm. How to write Claude Code hooks: events, matchers, blocking vs advisory, paths.

Its SKILL.md is about 2.9k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Agent Workflows, covering Hooks and plugins. The repository describes itself as: Natural-Language Programming Manager — scan, lint, and score NL artifacts with Claude-native quality scoring. The licence is ISC.

When your agent uses it

  • Tasks that involve Hooks and plugins

Example prompts

  • “/writing-hooks”

Workflow steps

8 steps, taken from the step headings in SKILL.md.

  1. Three Hook Types
  2. Blocking vs Advisory
  3. Event Selection Guide
  4. Matcher Patterns
  5. Portable Paths
  6. Fail-Open vs Fail-Closed
  7. Common Mistakes
  8. Quality Checklist

What it can do on your machine

Read from SKILL.md and the folder at commit 6fdbd05. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • jq

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Writing Hooks loads about 2.9k tokens when it runs. Until then it costs about 23 tokens; SKILL.md has 1,019 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~23
When it runs · the whole SKILL.md, loaded when a task matches
~2.9k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from xiaolai/nlpm at commit 6fdbd05, republished under its ISC licence (© xiaolai). 1,019 words, ~2,933 tokens.

Download SKILL.mdSave it as .claude/skills/writing-hooks/SKILL.md (or your agent's skills folder).
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

TypeWhat it doesWhen to useComplexity
commandRuns a shell script, reads JSON from stdinDeterministic checks: file existence, JSON validation, regex matchingMedium
promptInjects text into Claude's contextAdvisory: reminders, context injection, style guidanceLow
agentSpawns a verification agentComplex verification: code quality, semantic analysis, multi-file checksHigh
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
json
{
  "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
json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Remember: this project uses Result<T, E> for error handling. Never use try/catch directly."
          }
        ]
      }
    ]
  }
}
Agent Hook Example
json
{
  "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:

json
{
  "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:

json
{
  "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
SituationBlock or Advise?Rationale
Test failure on commitBlockBroken tests should never be committed
File over LOC limitBlockEnforce hard limit
Missing JSDoc on exportAdviseNice to have, not a hard requirement
No tests for new fileAdviseReminder, not a gate
Force push to mainBlockDestructive, irreversible
Large file creation (>500 lines)AdviseMight 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

EventWhen it firesCommon use cases
PreToolUseBefore a tool executesBlock dangerous operations, validate inputs, check preconditions
PostToolUseAfter a tool succeedsTrigger follow-up actions, lint changed files, update state
PostToolUseFailureAfter a tool failsError recovery, suggest alternatives, log failures
UserPromptSubmitWhen user sends a messageContext injection, session setup, mode activation
StopWhen Claude stops respondingCleanup, summary generation, state persistence
SessionStartSession beginsEnvironment validation, context loading, config checks
Event Selection by Goal
GoalEventHook type
Prevent bad writesPreToolUse + matcher Write|Editcommand
Lint after editPostToolUse + matcher Write|Editcommand
Inject project contextUserPromptSubmitprompt
Validate environment on startSessionStartcommand
Save session summary on exitStopagent
Recover from failed bash commandsPostToolUseFailure + matcher Bashprompt

4. Matcher Patterns

The matcher field uses regex to match tool names. It applies only to PreToolUse, PostToolUse, and PostToolUseFailure events.

PatternMatchesUse case
"Bash"Bash tool onlyGuard shell commands
"Write|Edit"Write or EditGuard file modifications
"Write"Write onlyGuard new file creation
"Edit"Edit onlyGuard file edits (not creation)
"Read"Read toolTrack what files Claude reads
"mcp__.*"All MCP tool callsGuard external integrations
"mcp__github__.*"GitHub MCP toolsGuard GitHub operations
"Task"Task tool (agent dispatch)Monitor agent dispatching
".*"EverythingUse carefully -- fires on every tool call
Show full SKILL.md (411 more words)Show less
Matcher Testing

Before deploying, verify your matcher with test cases:

MatcherShould matchShould NOT match
"Write|Edit"Write, EditBash, Read, WriteFile
"Bash"BashBashScript, mcp__bash
"mcp__github__.*"mcp__github__create_prmcp__slack__send

5. Portable Paths

Always use $+{CLAUDE_PLUGIN_ROOT} for script paths in hooks.json. This variable resolves to the plugin's installation directory at runtime.

Correct
json
{
  "command": "$+{CLAUDE_PLUGIN_ROOT}/scripts/check-loc.sh"
}
Wrong (breaks on other machines)
json
{
  "command": "/Users/joker/.claude/plugins/cache/xiaolai/my-plugin/0.1.0/scripts/check-loc.sh"
}
Script Location Convention
my-plugin/
  hooks/
    hooks.json          # hook definitions
  scripts/
    check-loc.sh        # hook scripts
    validate-config.sh
    lint-output.sh
Script Requirements

Every hook script must have:

  1. Shebang line: #!/bin/bash or #!/usr/bin/env node
  2. Executable permission: chmod +x scripts/*.sh
  3. JSON output: scripts must output valid JSON to stdout
  4. Stderr for logging: debug output goes to stderr, not stdout (stdout is parsed as JSON)
bash
#!/bin/bash
# Read input from stdin
input=$(cat)

# Debug logging goes to stderr
echo "Hook triggered: $(date)" >&2

# Business logic
file_path=$(echo "$input" | jq -r '.toolInput.file_path // empty')

if [ -z "$file_path" ]; then
  # Allow if we can't determine the file
  echo '{"hookSpecificOutput":{"decision":"allow"}}'
  exit 0
fi

loc=$(wc -l < "$file_path" 2>/dev/null || echo "0")

if [ "$loc" -gt 300 ]; then
  echo "{\"hookSpecificOutput\":{\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"File has $loc lines, exceeds 300 LOC limit\"}}"
else
  echo '{"hookSpecificOutput":{"decision":"allow"}}'
fi

6. Fail-Open vs Fail-Closed

What happens when your hook script crashes?

If the script crashes, allow the action. Safer for advisory hooks and non-critical checks.

bash
#!/bin/bash
# Fail-open wrapper
set +e  # Don't exit on error

result=$(your_check_logic 2>/dev/null)
exit_code=$?

if [ $exit_code -ne 0 ]; then
  # Script failed -- allow the action (fail-open)
  echo '{"hookSpecificOutput":{"decision":"allow"}}'
  exit 0
fi

# Normal processing...
echo "$result"
Fail-Closed (security-critical only)

If the script crashes, deny the action. Use only for critical security gates.

bash
#!/bin/bash
# Fail-closed wrapper
set +e

result=$(your_check_logic 2>/dev/null)
exit_code=$?

if [ $exit_code -ne 0 ]; then
  # Script failed -- deny the action (fail-closed)
  echo '{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"Safety check script failed -- blocking action as precaution"}}'
  exit 0
fi

# Normal processing...
echo "$result"
When to Use Each
Hook purposeFail modeRationale
LOC limit enforcementFail-openBetter to allow a large file than block all writes
Style reminderFail-openNon-critical advisory
Prevent force push to mainFail-closedDestructive action, err on side of caution
Secret detectionFail-closedSecurity-critical, must not leak
Test runnerFail-openTest infra failures shouldn't block development

7. Common Mistakes

MistakeWhy it's wrongFix
Blocking on PostToolUseAction already happened -- too late to blockUse PreToolUse for blocking
Wrong event casepretooluse instead of PreToolUse -- case-sensitiveUse exact case: PreToolUse, PostToolUse, etc.
Script not executableHook fails silentlyRun chmod +x scripts/*.sh
Missing shebangScript may run with wrong interpreterAdd #!/bin/bash or #!/usr/bin/env node
Hardcoded pathsBreaks on other machinesUse $+{CLAUDE_PLUGIN_ROOT}
stdout pollutionDebug output mixed into JSON responseUse stderr for logging: echo "debug" >&2
No timeoutSlow script blocks Claude indefinitelySet "timeout": 10000 (10 seconds)
Matcher too broad (".*")Fires on every tool call, performance impactNarrow to specific tools
No fail-open wrapperScript crash = broken hook = frustrated userWrap in fail-open try/catch

8. Quality Checklist

Before deploying hooks, verify:

  • Each hook has the correct event type for its purpose
  • Blocking hooks use PreToolUse, not PostToolUse
  • Matchers are tested against expected and unexpected tool names
  • All script paths use $+{CLAUDE_PLUGIN_ROOT}
  • All scripts have shebangs and executable permissions
  • All scripts output valid JSON to stdout
  • Debug logging goes to stderr, not stdout
  • Fail-open or fail-closed is explicitly chosen for each hook
  • Timeouts are set (default: 10 seconds)
  • Hooks are tested with: normal input, edge case input, missing input

© xiaolai, ISC. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in skills/nlpm/writing-hooks of xiaolai/nlpm.

Open the folder on GitHubat commit 6fdbd05

Compare with similar skills

Writing Hooks next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Writing Hooks compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Writing Hooks this skillxiaolai/nlpm146—~2.9kAutomated safety check: PassISC
Hook Development for Claude Code Pluginsanthropics/claude-plugins-official38k11 repos~4.1kAutomated safety check: NotesApache-2.0
Claude Code Agent Developmentanthropics/claude-plugins-official38k8 repos~2.8kAutomated safety check: PassApache-2.0
Claude Code Skill Developer Guidediet103/claude-code-infrastructure-showcase10k11 repos~3.5kAutomated safety check: PassMIT
Plugin Settings Patternanthropics/claude-plugins-official38k7 repos~3kAutomated safety check: PassApache-2.0
MCP Integration for Pluginsanthropics/claude-plugins-official38k11 repos~3.1kAutomated safety check: PassApache-2.0

Similar skills

  • Hook Development for Claude Code Plugins

    anthropics/claude-plugins-official

    Official

    Explains how to write Claude Code plugin hooks, both prompt-based checks and bash commands, for events such as PreToolUse, Stop and SessionStart.

    38k GitHub starsUsed in 11 repos~4.1k tokens
    Agent WorkflowsAuto-check: notes
  • Claude Code Agent Development

    anthropics/claude-plugins-official

    Official

    Explains how to write agents for Claude Code plugins: the markdown file with YAML frontmatter, trigger descriptions, model and color settings, and system prompt design.

    38k GitHub starsUsed in 8 repos~2.8k tokens
    Agent WorkflowsAuto-check passed
  • Claude Code Skill Developer Guide

    diet103/claude-code-infrastructure-showcase

    A guide to creating and managing Claude Code skills with auto-activation: skill-rules.json triggers, hooks, enforcement levels, YAML frontmatter and progressive disclosure.

    10k GitHub starsUsed in 11 repos~3.5k tokens
    Agent WorkflowsAuto-check passed
  • Plugin Settings Pattern

    anthropics/claude-plugins-official

    Official

    Shows how Claude Code plugins keep per-project settings and state in .claude/plugin-name.local.md files with YAML frontmatter and a markdown body.

    38k GitHub starsUsed in 7 repos~3k tokens
    Agent WorkflowsAuto-check passed
  • MCP Integration for Plugins

    anthropics/claude-plugins-official

    Official

    Explains how to bundle Model Context Protocol servers in a Claude Code plugin, covering config files, stdio, SSE, HTTP and WebSocket server types, and authentication.

    38k GitHub starsUsed in 11 repos~3.1k tokens
    Agent WorkflowsAuto-check passed
  • Claude Code Command Development

    anthropics/claude-plugins-official

    Official

    Explains how to write Claude Code slash commands: Markdown files with YAML frontmatter, arguments, file references, bash context and interactive prompts.

    38k GitHub starsUsed in 10 repos~4.8k tokens
    Agent WorkflowsAuto-check passed

More from xiaolai/nlpm

All 15 skills in this repo
  • Conventions

    xiaolai/nlpm

    Universal NL conventions: SKILL.md open spec, AGENTS.md, vague quantifiers, naming.

    146 GitHub stars~3.6k tokensUpdated yesterday
    Auto-check passed
  • Antigravity and Gemini CLI artifact schemas: .gemini/ paths, extensions, hooks.

    146 GitHub stars~3.1k tokensUpdated yesterday
    Auto-check passed
  • Conventions Codex

    xiaolai/nlpm

    Codex CLI artifact schemas: config.toml, .codex-plugin, skills, hooks, AGENTS.md.

    146 GitHub stars~4.9k tokensUpdated yesterday
    Auto-check passed
  • Orchestration

    xiaolai/nlpm

    Multi-agent workflow patterns: parallel dispatch, pipelines, QC gates, retries.

    146 GitHub stars~3k tokensUpdated yesterday
    Auto-check passed
  • Patterns

    xiaolai/nlpm

    NL artifact anti-patterns: vague quantifiers, bare prohibitions, oversized skills.

    146 GitHub stars~3.9k tokensUpdated yesterday
    Auto-check passed
  • Scoring

    xiaolai/nlpm

    100-point NL artifact rubric: penalty tables per artifact type, calibration cases.

    146 GitHub stars~5.3k tokensUpdated yesterday
    Auto-check passed

Categories

Questions about Writing Hooks

What does Writing Hooks do?

How to write Claude Code hooks: events, matchers, blocking vs advisory, paths. Writing Hooks is an agent skill from xiaolai/nlpm. How to write Claude Code hooks: events, matchers, blocking vs advisory, paths.

When should I use Writing Hooks?

Writing Hooks fits situations like: tasks that involve Hooks and plugins.

How do I install Writing Hooks in Claude Code?

Run `npx skills add xiaolai/nlpm --skill writing-hooks -a claude-code`. Or copy the skill folder (skills/nlpm/writing-hooks in xiaolai/nlpm) into .claude/skills/writing-hooks in your project. Claude Code loads it when a task matches its description.

How do I install Writing Hooks in Codex?

Run `npx skills add xiaolai/nlpm --skill writing-hooks -a codex`. Or copy the skill folder (skills/nlpm/writing-hooks in xiaolai/nlpm) into .agents/skills/writing-hooks in your project. Codex loads it when a task matches its description.

Can I use Writing Hooks in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add xiaolai/nlpm --skill writing-hooks -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/writing-hooks, .gemini/skills/writing-hooks, .github/skills/writing-hooks and .opencode/skills/writing-hooks in your project.

What does Writing Hooks need to run?

Going by SKILL.md and its folder, Writing Hooks needs the command-line tools its instructions call (jq).

Does Writing Hooks access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Writing Hooks safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Writing Hooks use?

Writing Hooks is published under the ISC licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Writing Hooks use?

About 2.9k tokens (SKILL.md is roughly 12k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Writing Hooks?

Skills that share tags, products or a category with Writing Hooks: Hook Development for Claude Code Plugins (anthropics/claude-plugins-official, 38k stars), Claude Code Agent Development (anthropics/claude-plugins-official, 38k stars), Claude Code Skill Developer Guide (diet103/claude-code-infrastructure-showcase, 10k stars) and Plugin Settings Pattern (anthropics/claude-plugins-official, 38k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Writing Hooks?

xiaolai (a GitHub user) maintains it in xiaolai/nlpm, which has 146 GitHub stars. The repository holds 15 skills in this directory. The repository was last updated on October 8, 2026.

Source: xiaolai/nlpm on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.