AgentStack
SKILL verified MIT Self-run

Creating Hooks

skill-jack-michaud-faire-creating-hooks · by jack-michaud

Build event-driven hooks in Claude Code for validation, setup, and automation. Use when you need to validate inputs, check environment state, or automate tasks at specific lifecycle events.

No reviews yet
0 installs
18 views
0.0% view→install

Install

$ agentstack add skill-jack-michaud-faire-creating-hooks

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Creating Hooks? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Creating Hooks

Overview

Hooks are event-driven scripts that execute at specific points in Claude Code's lifecycle. They receive JSON input with session data and event-specific information, enabling validation, environment checks, and automated workflows.

When to Use

  • Validate tool inputs before execution (PreToolUse)
  • Verify outputs after tool completion (PostToolUse)
  • Check environment state before processing prompts (UserPromptSubmit)
  • Initialize resources at session startup (SessionStart)
  • Clean up resources at session end (SessionEnd)

Hook Types

PreToolUse

Runs before tool execution. Use for:

  • Environment validation (check dependencies exist)
  • Input validation (verify paths, parameters)
  • Permission checks (ensure access rights)
  • State verification (git status, working directory)

PostToolUse

Runs after tool completion. Use for:

  • Output validation (verify file changes)
  • Quality checks (run linters, formatters)
  • Side effects (update logs, metrics)
  • Failure detection (check for errors)

UserPromptSubmit

Runs before processing user input. Use for:

  • Input sanitization
  • Context injection
  • Usage tracking
  • Cost estimation

SessionStart

Runs at session initialization. Use for:

  • Environment setup
  • Dependency checks
  • Configuration loading
  • Initialization logging

SessionEnd

Runs at session termination. Use for:

  • Cleanup tasks
  • Result archiving
  • Metrics reporting
  • Resource deallocation

JSON Input Structure

Common Fields (All Events)

{
  "session_id": "unique-session-identifier",
  "transcript_path": "/path/to/conversation.json",
  "cwd": "/current/working/directory",
  "hook_event_name": "PreToolUse|PostToolUse|UserPromptSubmit|SessionStart|SessionEnd"
}

Event-Specific Fields

PreToolUse:

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "pytest tests/",
    "description": "Run test suite"
  }
}

PostToolUse:

{
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "/path/to/file.py",
    "old_string": "...",
    "new_string": "..."
  },
  "tool_response": {
    "success": true,
    "message": "File edited successfully"
  }
}

UserPromptSubmit:

{
  "prompt": "User's input text here"
}

SessionStart:

{
  "source": "startup|resume"
}

SessionEnd:

{
  "reason": "user_exit|error|timeout"
}

Configuration

Path Resolution with CLAUDEPROJECTDIR

Always use $CLAUDE_PROJECT_DIR to reference hook scripts. Claude Code sets this environment variable to your project root, ensuring hooks work regardless of the current working directory.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
          }
        ]
      }
    ]
  }
}

Why this matters:

  • Claude's CWD can change during execution
  • Relative paths like ./scripts/hook.sh become fragile
  • $CLAUDE_PROJECT_DIR always points to your project root
  • Ensures hooks work from any directory

Note: The environment variable is only available when Claude Code spawns the hook command.

Settings File Integration

Add hooks to .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/scripts/pre-tool-hook.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/scripts/validate-edits.sh"
          }
        ]
      }
    ]
  }
}

Matcher Patterns

  • * - Match all tools
  • Edit - Match specific tool
  • Edit|Write - Match multiple tools
  • Bash(git:*) - Match tool with pattern

Process

  1. Identify the trigger event - Which lifecycle point needs automation?
  2. Design hook script - What validation or action is needed?
  3. Parse JSON input - Extract relevant fields from stdin
  4. Implement logic - Perform checks or automation
  5. Return exit code - 0 for success, non-zero blocks execution
  6. Add to settings.json - Configure hook with matcher
  7. Test hook - Trigger event and verify behavior

Examples

Example 1: Pre-Tool Git Status Check

Use case: Warn if working directory is dirty before file operations

#!/bin/bash
# scripts/pre-tool-git-check.sh

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

if [[ "$TOOL_NAME" == "Edit" || "$TOOL_NAME" == "Write" ]]; then
  if ! git diff-index --quiet HEAD --; then
    echo "⚠️  Warning: Uncommitted changes in working directory"
    echo "Consider committing before editing files"
  fi
fi

exit 0  # Don't block, just warn

Configuration:

{
  "PreToolUse": [{
    "matcher": "Edit|Write",
    "hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/scripts/pre-tool-git-check.sh"}]
  }]
}

Example 2: Post-Tool Code Formatting

Use case: Auto-format Python files after editing

#!/bin/bash
# scripts/post-edit-format.sh

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path')

if [[ "$TOOL_NAME" == "Edit" && "$FILE_PATH" == *.py ]]; then
  black "$FILE_PATH" --quiet
  echo "✅ Formatted $FILE_PATH with black"
fi

exit 0

Example 3: Session Start Environment Check

Use case: Verify dependencies exist before starting

#!/bin/bash
# scripts/session-start-check.sh

MISSING=()

command -v python >/dev/null || MISSING+=("python")
command -v git >/dev/null || MISSING+=("git")
command -v jq >/dev/null || MISSING+=("jq")

if [ ${#MISSING[@]} -gt 0 ]; then
  echo "❌ Missing dependencies: ${MISSING[*]}"
  exit 1  # Block session
fi

echo "✅ All dependencies available"
exit 0

Best Practices

Path Configuration

  • Do: Always use $CLAUDE_PROJECT_DIR for hook script paths
  • Do: Quote the path: "$CLAUDE_PROJECT_DIR"/scripts/hook.sh
  • Don't: Use relative paths like ./scripts/hook.sh (breaks if CWD changes)
  • Don't: Use absolute paths like /home/user/project/... (not portable)

Design

  • Do: Keep hooks fast (<1 second)
  • Do: Use specific matchers to reduce overhead
  • Do: Return non-zero to block execution
  • Don't: Perform expensive operations in hooks
  • Don't: Block on warnings (use exit 0)

Error Handling

  • Do: Provide clear error messages
  • Do: Log hook failures for debugging
  • Do: Handle missing JSON fields gracefully
  • Don't: Fail silently
  • Don't: Assume JSON structure without validation

JSON Parsing

  • Do: Use jq for robust JSON parsing
  • Do: Provide default values for optional fields
  • Do: Validate required fields exist
  • Don't: Use regex to parse JSON
  • Don't: Assume fields are always present

Performance

  • Do: Exit early when hook doesn't apply
  • Do: Cache expensive checks when possible
  • Do: Use narrow matchers to reduce invocations
  • Don't: Run hooks on every tool unconditionally
  • Don't: Perform network requests without caching

Integration Patterns

With Git

# Check for uncommitted changes
git diff-index --quiet HEAD --

# Get current branch
git branch --show-current

# Check if file is tracked
git ls-files --error-unmatch "$FILE_PATH"

With Linters

# Python
pylint "$FILE_PATH" --score=no --msg-template='{msg_id}: {msg}'

# JavaScript
eslint "$FILE_PATH" --format=compact

# Go
golint "$FILE_PATH"

With Testing

# Run tests related to changed file
pytest "tests/test_${FILENAME}" --quiet

# Fast syntax check only
python -m py_compile "$FILE_PATH"

Common Use Cases

Validation Hooks

  • Verify environment variables set
  • Check file permissions
  • Validate input parameters
  • Ensure dependencies installed

Quality Hooks

  • Run linters on edited files
  • Format code automatically
  • Check test coverage
  • Validate commit messages

Workflow Hooks

  • Update documentation
  • Regenerate configuration
  • Sync database schemas
  • Trigger CI/CD pipelines

Monitoring Hooks

  • Log tool usage
  • Track session metrics
  • Report errors
  • Update dashboards

Anti-patterns

  • Don't: Use relative or absolute paths for hook commands
  • Do: Use $CLAUDE_PROJECT_DIR for portable, reliable paths
  • Don't: Use hooks for long-running tasks
  • Do: Keep hooks under 1 second
  • Don't: Block on non-critical checks
  • Do: Use exit 0 for warnings
  • Don't: Parse JSON with string manipulation
  • Do: Use jq for reliable parsing
  • Don't: Match all tools without filtering
  • Do: Use specific matchers for relevant tools
  • Don't: Ignore hook failures silently
  • Do: Provide clear feedback to user

Constraints

  • Never inline complex regex directly in hooks.json — double-escaping across JSON and bash layers corrupts the pattern; always delegate to a script file
  • Never use \s in grep ERE patterns — use [[:space:]] instead; \s is not supported
  • Never rely on \b word boundaries in grep ERE — they are unreliable; use explicit character class delimiters instead
  • Never test blocking hooks without also testing false positives alongside bypass vectors — a hook that over-blocks is as broken as one that under-blocks
  • Always define long regex as a PATTERN='...' variable in the script rather than embedding it inline in the grep call — it avoids a third layer of quoting and is far easier to read and update

Resources

  • Official Docs: https://docs.claude.com/en/docs/claude-code/hooks
  • Settings Reference: https://docs.claude.com/en/docs/claude-code/settings
  • JSON Parsing: man jq or https://jqlang.github.io/jq/

Custom docs written on creating-hooks topics:

  • resources/testing-pretooluse-blocking-hooks.md

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.