Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
parcadei avatar

Hook Developer

  • 485 installs
  • 3.9k repo stars
  • Updated January 26, 2026
  • parcadei/continuous-claude-v3

hook-developer is a Claude Code skill that provides a complete reference for writing Claude Code hooks with correct input/output schemas, registration, and testing patterns.

About

hook-developer from continuous-claude-v3 is the authoritative Claude Code hooks reference for developers extending agent behavior. It documents when each hook fires, whether it can block tool execution, and primary use cases in a quick-reference table covering PreToolUse and PostToolUse among others. Developers use hook-developer when creating hooks, debugging input/output formats, registering hooks in settings.json, or learning which hooks block versus inject context. The skill ensures custom developer workflows trigger automatically at the correct moment during long-running sessions.

  • Automatically triggers developer hooks based on conversation context
  • Enables continuous Claude sessions that self-extend with custom capabilities
  • Reduces context-window waste by calling tools only when relevant
  • Supports custom hook definitions for project-specific automation

Hook Developer by the numbers

  • 485 all-time installs (skills.sh)
  • +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
  • Ranked #1,801 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/parcadei/continuous-claude-v3 --skill hook-developer

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs485
repo stars3.9k
Last updatedJanuary 26, 2026
Repositoryparcadei/continuous-claude-v3

How do you write Claude Code hooks with correct schemas?

Let Claude automatically invoke custom developer tools and workflows at the right moment during long-running coding sessions.

Who is it for?

Developers extending Claude Code with custom hooks who need schema-accurate registration and testing guidance.

Skip if: Non-Claude agent platforms without hook APIs, or one-off scripts that do not integrate with Claude Code lifecycle events.

When should I use this skill?

Creating a new Claude Code hook, debugging hook I/O, or configuring hook registration in settings.json.

What you get

Registered Claude Code hooks, validated input/output schemas, and tested block-or-context-injection behavior.

  • Hook implementation
  • settings.json registration
  • Hook test validation

Files

SKILL.mdMarkdownGitHub ↗

Hook Developer

Complete reference for developing Claude Code hooks. Use this to write hooks with correct input/output schemas.

When to Use

  • Creating a new hook
  • Debugging hook input/output format
  • Understanding what fields are available
  • Setting up hook registration in settings.json
  • Learning what hooks can block vs inject context

Quick Reference

HookFires WhenCan Block?Primary Use
PreToolUseBefore tool executesYESBlock/modify tool calls
PostToolUseAfter tool completesPartialReact to tool results
UserPromptSubmitUser sends promptYESValidate/inject context
PermissionRequestPermission dialog showsYESAuto-approve/deny
SessionStartSession beginsNOLoad context, set env vars
SessionEndSession endsNOCleanup/save state
StopAgent finishesYESForce continuation
SubagentStartSubagent spawnsNOPattern coordination
SubagentStopSubagent finishesYESForce continuation
PreCompactBefore compactionNOSave state
NotificationNotification sentNOCustom alerts

Hook type options: type: "command" (bash) or type: "prompt" (LLM evaluation)

---

Hook Input/Output Schemas

PreToolUse

Purpose: Block or modify tool execution before it happens.

Input:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "default|plan|acceptEdits|bypassPermissions",
  "hook_event_name": "PreToolUse",
  "tool_name": "string",
  "tool_input": {
    "file_path": "string",
    "command": "string"
  },
  "tool_use_id": "string"
}

Output (JSON):

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow|deny|ask",
    "permissionDecisionReason": "string",
    "updatedInput": {}
  },
  "continue": true,
  "stopReason": "string",
  "systemMessage": "string",
  "suppressOutput": true
}

Exit code 2: Blocks tool, stderr shown to Claude.

Common matchers: Bash, Edit|Write, Read, Task, mcp__.*

---

PostToolUse

Purpose: React to tool execution results, provide feedback to Claude.

Input:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "PostToolUse",
  "tool_name": "string",
  "tool_input": {},
  "tool_response": {
    "filePath": "string",
    "success": true,
    "output": "string",
    "exitCode": 0
  },
  "tool_use_id": "string"
}

CRITICAL: The response field is tool_response, NOT tool_result.

Output (JSON):

{
  "decision": "block",
  "reason": "string",
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "string"
  },
  "continue": true,
  "stopReason": "string",
  "suppressOutput": true
}

Blocking: "decision": "block" with "reason" prompts Claude to address the issue.

Common matchers: Edit|Write, Bash

---

UserPromptSubmit

Purpose: Validate user prompts, inject context before Claude processes.

Input:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "string"
}

Output (Plain text):

Any stdout text is added to context for Claude.

Output (JSON):

{
  "decision": "block",
  "reason": "string",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "string"
  }
}

Blocking: "decision": "block" erases prompt, shows "reason" to user only (not Claude).

Exit code 2: Blocks prompt, shows stderr to user only.

---

PermissionRequest

Purpose: Automate permission dialog decisions.

Input:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "PermissionRequest",
  "tool_name": "string",
  "tool_input": {}
}

Output:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow|deny",
      "updatedInput": {},
      "message": "string",
      "interrupt": false
    }
  }
}

---

SessionStart

Purpose: Initialize session, load context, set environment variables.

Input:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "SessionStart",
  "source": "startup|resume|clear|compact"
}

Environment variable: CLAUDE_ENV_FILE - write export VAR=value to persist env vars.

Output (Plain text or JSON):

{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "string"
  },
  "suppressOutput": true
}

Plain text stdout is added as context.

---

SessionEnd

Purpose: Cleanup, save state, log session.

Input:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "SessionEnd",
  "reason": "clear|logout|prompt_input_exit|other"
}

Output: Cannot affect session (already ending). Use for cleanup only.

---

Stop

Purpose: Control when Claude stops, force continuation.

Input:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "Stop",
  "stop_hook_active": false
}

CRITICAL: Check stop_hook_active: true to prevent infinite loops!

Output:

{
  "decision": "block",
  "reason": "string"
}

Blocking: "decision": "block" forces Claude to continue with "reason" as prompt.

---

SubagentStart

Purpose: Run when a subagent (Task tool) is spawned.

Input:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "SubagentStart",
  "agent_id": "string"
}

Output: Context injection only (cannot block).

---

SubagentStop

Purpose: Control when subagents (Task tool) stop.

Input:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "SubagentStop",
  "stop_hook_active": false
}

Output: Same as Stop.

---

PreCompact

Purpose: Save state before context compaction.

Input:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "PreCompact",
  "trigger": "manual|auto",
  "custom_instructions": "string"
}

Matchers: manual, auto

Output:

{
  "continue": true,
  "systemMessage": "string"
}

---

Notification

Purpose: Custom notification handling.

Input:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "Notification",
  "message": "string",
  "notification_type": "permission_prompt|idle_prompt|auth_success|elicitation_dialog"
}

Matchers: permission_prompt, idle_prompt, auth_success, elicitation_dialog, *

Output:

{
  "continue": true,
  "suppressOutput": true,
  "systemMessage": "string"
}

---

Registration in settings.json

Standard Structure

{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

Matcher Patterns

PatternMatches
BashExactly Bash tool
`Edit\Write`
Read.*Regex: Read*
mcp__.*__write.*MCP write tools
*All tools

Case-sensitive: Bashbash

Events Requiring Matchers

  • PreToolUse - YES (required)
  • PostToolUse - YES (required)
  • PermissionRequest - YES (required)
  • Notification - YES (optional)
  • SessionStart - YES (startup|resume|clear|compact)
  • PreCompact - YES (manual|auto)

Events Without Matchers

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [{ "type": "command", "command": "/path/to/hook.sh" }]
      }
    ]
  }
}

---

Hook Types

Command Hooks (type: "command")

Default type. Executes bash commands or scripts.

{
  "type": "command",
  "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh",
  "timeout": 60
}

Prompt-Based Hooks (type: "prompt")

Uses LLM (Haiku) for context-aware decisions. Best for Stop/SubagentStop.

{
  "type": "prompt",
  "prompt": "Evaluate if Claude should stop. Context: $ARGUMENTS. Check if all tasks are complete.",
  "timeout": 30
}

Response schema:

{
  "decision": "approve" | "block",
  "reason": "Explanation",
  "continue": false,
  "stopReason": "Message to user",
  "systemMessage": "Warning"
}

MCP Tool Naming

MCP tools use pattern mcp__<server>__<tool>:

PatternMatches
mcp__memory__.*All memory server tools
mcp__.*__write.*All MCP write tools
mcp__github__.*All GitHub tools

---

Environment Variables

Available to All Hooks

VariableDescription
CLAUDE_PROJECT_DIRAbsolute path to project root
CLAUDE_CODE_REMOTE"true" if remote/web, empty if local CLI

SessionStart Only

VariableDescription
CLAUDE_ENV_FILEPath to write export VAR=value lines

Plugin Hooks Only

VariableDescription
CLAUDE_PLUGIN_ROOTAbsolute path to plugin directory

---

Exit Codes

Exit CodeBehaviorstdoutstderr
0SuccessJSON processedIgnored
2Blocking errorIGNOREDError message
OtherNon-blocking errorIgnoredVerbose mode

Exit Code 2 by Hook

HookEffect
PreToolUseBlocks tool, stderr to Claude
PostToolUsestderr to Claude (tool already ran)
UserPromptSubmitBlocks prompt, stderr to user only
StopBlocks stop, stderr to Claude

---

Shell Wrapper Pattern

#!/bin/bash
set -e
cd "$CLAUDE_PROJECT_DIR/.claude/hooks"
cat | npx tsx src/my-hook.ts

Or for bundled:

#!/bin/bash
set -e
cd "$HOME/.claude/hooks"
cat | node dist/my-hook.mjs

---

TypeScript Handler Pattern

import { readFileSync } from 'fs';

interface HookInput {
  session_id: string;
  hook_event_name: string;
  tool_name?: string;
  tool_input?: Record<string, unknown>;
  tool_response?: Record<string, unknown>;
  // ... other fields per hook type
}

function readStdin(): string {
  return readFileSync(0, 'utf-8');
}

async function main() {
  const input: HookInput = JSON.parse(readStdin());

  // Process input

  const output = {
    decision: 'block',  // or undefined to allow
    reason: 'Why blocking'
  };

  console.log(JSON.stringify(output));
}

main().catch(console.error);

---

Testing Hooks

Manual Test Commands

# PostToolUse (Write)
echo '{"tool_name":"Write","tool_input":{"file_path":"test.md"},"tool_response":{"success":true},"session_id":"test"}' | \
  .claude/hooks/my-hook.sh

# PreToolUse (Bash)
echo '{"tool_name":"Bash","tool_input":{"command":"ls"},"session_id":"test"}' | \
  .claude/hooks/my-hook.sh

# SessionStart
echo '{"hook_event_name":"SessionStart","source":"startup","session_id":"test"}' | \
  .claude/hooks/session-start.sh

# SessionEnd
echo '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"test"}' | \
  .claude/hooks/session-end.sh

# UserPromptSubmit
echo '{"prompt":"test prompt","session_id":"test"}' | \
  .claude/hooks/prompt-submit.sh

Rebuild After TypeScript Edits

cd .claude/hooks
npx esbuild src/my-hook.ts \
  --bundle --platform=node --format=esm \
  --outfile=dist/my-hook.mjs

---

Common Patterns

Block Dangerous Files (PreToolUse)

#!/usr/bin/env python3
import json, sys

data = json.load(sys.stdin)
path = data.get('tool_input', {}).get('file_path', '')

BLOCKED = ['.env', 'secrets.json', '.git/']
if any(b in path for b in BLOCKED):
    print(json.dumps({
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "deny",
            "permissionDecisionReason": f"Blocked: {path} is protected"
        }
    }))
else:
    print('{}')

Auto-Format Files (PostToolUse)

#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [[ "$FILE" == *.ts ]] || [[ "$FILE" == *.tsx ]]; then
  npx prettier --write "$FILE" 2>/dev/null
fi

echo '{}'

Inject Git Context (UserPromptSubmit)

#!/bin/bash
echo "Git status:"
git status --short 2>/dev/null || echo "(not a git repo)"
echo ""
echo "Recent commits:"
git log --oneline -5 2>/dev/null || echo "(no commits)"

Force Test Verification (Stop)

#!/usr/bin/env python3
import json, sys, subprocess

data = json.load(sys.stdin)

# Prevent infinite loops
if data.get('stop_hook_active'):
    print('{}')
    sys.exit(0)

# Check if tests pass
result = subprocess.run(['npm', 'test'], capture_output=True)
if result.returncode != 0:
    print(json.dumps({
        "decision": "block",
        "reason": "Tests are failing. Please fix before stopping."
    }))
else:
    print('{}')

---

Debugging Checklist

  • [ ] Hook registered in settings.json?
  • [ ] Shell script has +x permission?
  • [ ] Bundle rebuilt after TS changes?
  • [ ] Using tool_response not tool_result?
  • [ ] Output is valid JSON (or plain text)?
  • [ ] Checking stop_hook_active in Stop hooks?
  • [ ] Using $CLAUDE_PROJECT_DIR for paths?

---

Key Learnings from Past Sessions

1. Field names matter - tool_response not tool_result 2. Output format - decision: "block" + reason for blocking 3. Exit code 2 - stderr goes to Claude/user, stdout IGNORED 4. Rebuild bundles - TypeScript source edits don't auto-apply 5. Test manually - echo '{}' | ./hook.sh before relying on it 6. Check outputs first - ls .claude/cache/ before editing code 7. Detached spawn hides errors - add logging to debug

See Also

  • /debug-hooks - Systematic debugging workflow
  • .claude/rules/hooks.md - Hook development rules

Related skills

FAQ

Can Claude Code hooks block tool execution?

hook-developer notes PreToolUse fires before a tool executes and CAN block or modify tool calls. PostToolUse fires after execution. The skill's quick-reference table lists block capability per hook type.

Where are Claude Code hooks registered?

hook-developer covers hook registration in settings.json plus input/output schema requirements. Developers use it when creating hooks, debugging formats, or learning available hook fields.

AI & Agent Buildingagentsautomation

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.