Receber por email
← Voltar para o catálogo

hook-development

Use ao criar, modificar ou depurar hooks do Claude Code, como PreToolUse, PostToolUse e outros. Abrange formatos de wrapper de plugin e diretos do usuário, matchers, padrões de segurança, portabilidade, limitações de ciclo de vida e depuração.

34estrelas
Atualizado há 4 meses

Ver no GitHub ↗ Licença: MIT

Como adicionar

/plugin marketplace add Kanevry/session-orchestrator

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/hook-development-kanevry/svg)](https://www.skillteca.com.br/skills/hook-development-kanevry)

Skills relacionadas

Ver todas de Desenvolvimento →

Alerta por categoria

Receba novas skills de Desenvolvimento toda segunda

1 email curto, só com as skills novas de Desenvolvimento. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

Hook Development for Claude Code Plugins

Adapted from claude-plugins-official/plugin-dev/skills/hook-development. Trimmed to what we actually author (our plugin already has 6 event matchers covering 7 hook handlers — see hooks/hooks.json).

Hook types

Prompt-based (LLM-driven, for complex reasoning)

{
  "type": "prompt",
  "prompt": "Evaluate if this tool use is appropriate: $TOOL_INPUT",
  "timeout": 30
}

Supported events: Stop, SubagentStop, UserPromptSubmit, PreToolUse.

Use for: context-aware decisions, flexible evaluation, natural-language reasoning.

Command (deterministic, for fast checks)

{
  "type": "command",
  "command": "${CLAUDE_PLUGIN_ROOT}/hooks/validate.mjs",
  "timeout": 60
}

Use for: fast deterministic validations, file-system ops, external tools, performance-critical paths.

Our convention: all our command hooks are .mjs (Node.js) — see hooks/pre-bash-destructive-guard.mjs, hooks/enforce-scope.mjs. The v3.0 migration moved us off bash for native Windows support.

Configuration formats

This is where people trip up. Two formats exist; they are NOT interchangeable.

Plugin hooks/hooks.json — wrapper format

{
  "description": "Plugin hook description (optional)",
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/validate.mjs" }
        ]
      }
    ]
  }
}
  • hooks wrapper is required
  • description is optional

User .claude/settings.json — direct format

{
  "PreToolUse": [
    {
      "matcher": "Write|Edit",
      "hooks": [
        { "type": "command", "command": "~/my-hook.sh" }
      ]
    }
  ]
}
  • No wrapper
  • No description

Mixing these up is the #1 reason new hooks don't fire.

Hook events

| Event | When | Use for | |-------|------|---------| | PreToolUse | Before tool runs | Validate, modify, block | | PostToolUse | After tool completes | React to result, log | | UserPromptSubmit | User submits prompt | Add context, validate | | Stop | Main agent stopping | Completeness check | | SubagentStop | Subagent stopping | Task validation | | SessionStart | Session begins | Context load | | SessionEnd | Session ends | Cleanup, logging | | PreCompact | Before compaction | Preserve critical state | | Notification | User notified | Logging, reactions |

PreToolUse output schema

{
  "hookSpecificOutput": {
    "permissionDecision": "allow|deny|ask",
    "updatedInput": { "field": "modified_value" }
  },
  "systemMessage": "Explanation shown to Claude"
}

Stop / SubagentStop output

{
  "decision": "approve|block",
  "reason": "Why blocked / approved",
  "systemMessage": "Additional context"
}

SessionStart: persist env vars

echo "export PROJECT_TYPE=nodejs" >> "$CLAUDE_ENV_FILE"

$CLAUDE_ENV_FILE is unique to SessionStart hooks.

Input schema

All hooks receive JSON on stdin:

{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/current/working/dir",
  "permission_mode": "ask|allow",
  "hook_event_name": "PreToolUse"
}

Event-specific extras:

  • PreToolUse/PostToolUse: tool_name, tool_input, tool_result
  • UserPromptSubmit: user_prompt
  • Stop/SubagentStop: reason

Access in prompt hooks via $TOOL_INPUT, $TOOL_RESULT, $USER_PROMPT.

Environment variables

| Var | Scope | Purpose | |-----|-------|---------| | $CLAUDE_PROJECT_DIR | All | Project root | | $CLAUDE_PLUGIN_ROOT | Plugin hooks | Plugin directory — use this, never hardcode paths | | $CLAUDE_ENV_FILE | SessionStart only | Persist env vars | | $CLAUDE_CODE_REMOTE | All (conditional) | Set if running remote |

Portability rule

// ✅ Portable — works everywhere the plugin installs
{ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/guard.mjs" }

// ❌ Broken — only works on the operator's machine
{ "command": "~/Projects/.../guard.mjs" }

Matchers

"matcher": "Write"                       // Exact tool
"matcher": "Read|Write|Edit"             // Multiple
"matcher": "*"                           // All tools
"matcher": "mcp__.*__delete.*"           // Regex — all MCP delete tools
"matcher": "mcp__gitlab_.*"              // Specific MCP server

Matchers are case-sensitive.

Security best practices

Validate inputs (command hooks)

#!/bin/bash
set -euo pipefail

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

if [[ ! "$tool_name" =~ ^[a-zA-Z0-9_]+$ ]]; then
  echo '{"decision": "deny", "reason": "Invalid tool name"}' >&2
  exit 2
fi

In Node/.mjs hooks (our convention), same principle — parse stdin JSON, validate structure before trusting.

Path safety

file_path=$(echo "$input" | jq -r '.tool_input.file_path')

# Deny path traversal
[[ "$file_path" == *".."* ]] && { echo '{"decision":"deny","reason":"Path traversal"}' >&2; exit 2; }

# Deny sensitive files
[[ "$file_path" == *".env"* ]] && { echo '{"decision":"deny","reason":"Sensitive file"}' >&2; exit 2; }

Our enforce-scope.mjs implements this for wave-scope boundaries.

Quote variables

echo "$file_path"        # ✅
cd "$CLAUDE_PROJECT_DIR" # ✅
echo $file_path          # ❌ unquoted injection risk

Timeouts

Defaults: command hooks 60s, prompt hooks 30s. Set explicitly when the work is known-slow:

{ "type": "command", "command": "...", "timeout": 10 }

Parallel execution

All matching hooks run in parallel — they don't see each other's output, ordering is non-deterministic. Design for independence.

Lifecycle limitation — NO hot-swap

Hooks load at session start. Changes to hooks.json or hook scripts do not affect the running session.

To test hook changes:

  1. Edit hook
  2. Exit Claude Code
  3. Restart (claude or cc)
  4. Verify with /hooks command or claude --debug

This is the #2 reason "my hook isn't working" — the change hasn't loaded yet.

Debugging

Debug mode

claude --debug

Surfaces hook registration, execution logs, stdin/stdout JSON, timing.

Test a command hook directly

echo '{"tool_name":"Write","tool_input":{"file_path":"/test"}}' | \
  ${CLAUDE_PLUGIN_ROOT}/hooks/guard.mjs
echo "Exit code: $?"

Validate JSON output

output=$(./your-hook.mjs < test-input.json)
echo "$output" | jq .

Invalid JSON breaks silently — always verify.

Conditional activation

Pattern: check for a flag file or config before running:

#!/bin/bash
FLAG_FILE="$CLAUDE_PROJECT_DIR/.enable-strict-validation"
[[ ! -f "$FLAG_FILE" ]] && exit 0   # Flag not present, skip
# ... validation logic

Or config-based (matches our Session-Config pattern):

CONFIG_FILE="$CLAUDE_PROJECT_DIR/.claude/config.json"
enabled=$(jq -r '.strictMode // false' "$CONFIG_FILE" 2>/dev/null)
[[ "$enabled" != "true" ]] && exit 0

Our in-house examples (read these, not the upstream examples/)

  • hooks/pre-bash-destructive-guard.mjs — policy-driven command blocker, 13 rules in .orchestrator/policy/blocked-commands.json
  • hooks/enforce-scope.mjs — wave-scope boundary enforcement using .orchestrator/wave-scope.json
  • hooks/on-session-start.mjs — banner + session init
  • hooks/post-edit-validate.mjs — validates edits after the fact
  • hooks/on-stop.mjs — session-event capture + metrics

Do / Don't

Do:

  • Prompt-based hooks for complex logic, command hooks for fast deterministic checks
  • Always ${CLAUDE_PLUGIN_ROOT} for paths
  • Validate every input field before trusting it
  • Quote all shell variables
  • Set explicit timeouts for known-slow work
  • Return structured JSON on stdout

Don't:

  • Hardcoded paths
  • Trust tool_input without validation
  • Long-running ho
Compartilhar X LinkedIn