Skip to content
Development
Skill

/hook-development

Use when creating, modifying, or debugging Claude Code hooks — PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification. Covers the plugin `hooks/hooks.json` wrapper format vs. the user `settings.json` direct format,

From plugin
session-orchestrator
5144 skills14 agents26 commands10 hooks
+1
Install
$ npx -y skills add Kanevry/session-orchestrator --skill hook-development --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/hook-development

Context preview

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

Use when creating, modifying, or debugging Claude Code hooks — PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification. Covers the plugin `hooks/hooks.json` wrapper format vs. the user `settings.json` direct format,

SKILL.md

hook-development.SKILL.md
name: hook-development
description: Use when creating, modifying, or debugging Claude Code hooks — PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification. Covers the plugin `hooks/hooks.json` wrapper format vs. the user `settings.json` direct format, matchers, security patterns, `$CLAUDE_PLUGIN_ROOT` portability, lifecycle limitations, and debugging. Trigger on "add a hook", "validate tool use", "block dangerous commands", "enforce completion", "hook-based automation".
model: sonnet

Hook Development for Claude Code Plugins

Use the [official Claude Code hooks reference](https://code.claude.com/docs/en/hooks) as the source of truth for current events and schemas. This skill keeps only the conventions needed to author this plugin's hooks.

Hook types used in this plugin

Claude Code also documents `http`, `mcp_tool`, and experimental `agent` handlers. Use those only after checking their current fields and event support in the official reference.

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

{
  "type": "prompt",
  "prompt": "Evaluate whether this event should proceed: $ARGUMENTS",
  "timeout": 30
}

Prompt hooks are supported only on events documented for that handler type. `$ARGUMENTS` contains the hook input JSON.

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:** hook logic lives in `.mjs` files — see `hooks/pre-bash-destructive-guard.mjs` and `hooks/enforce-scope.mjs`. The manifest invokes them through the repository's runtime wrapper.

Configuration formats

Keep the file location and its outer document shape explicit when copying an example.

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 or project `.claude/settings.json` — settings format

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          { "type": "command", "command": "~/my-hook.sh" }
        ]
      }
    ]
  }
}
  • The top-level `hooks` key is required in settings.
  • Plugin `hooks/hooks.json` may additionally carry a top-level `description`.

The distinction is registration and scope: settings hooks belong to a user, project, or managed policy; plugin hooks run while the plugin is enabled. The nested event → matcher group → handler shape is the same.

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 or resumes | Context load | | `SessionEnd` | Session ends | Cleanup, logging | | `PreCompact` | Before compaction | Preserve critical state | | `Notification` | User notified | Logging, reactions |

PreToolUse output schema

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Why this decision was made",
    "updatedInput": { "field": "modified_value" }
  },
  "systemMessage": "Explanation shown to Claude"
}

Stop / SubagentStop output

{
  "decision": "block",
  "reason": "Why Claude should continue",
  "systemMessage": "Additional context"
}

Omit `decision` to allow stopping. `approve` is not a valid Stop decision. For non-error feedback that keeps the conversation running, use `hookSpecificOutput.additionalContext` with `hookEventName` set to `Stop` or `SubagentStop`.

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": "default",
  "hook_event_name": "PreToolUse"
}

Event-specific extras:

  • `PreToolUse`: `tool_name`, `tool_input`, `tool_use_id`
  • `PostToolUse`: `tool_name`, `tool_input`, `tool_response`, `tool_use_id`
  • `UserPromptSubmit`: `prompt`
  • `Stop`: `stop_hook_active`, `last_assistant_message`; `SubagentStop` also carries agent identity and transcript fields

Event fields vary and evolve. Parse only fields needed by the hook and consult the official event section before depending on one. The `UserPromptSubmit` field name above was last checked against the official reference on 2026-09-15 (branch `codex/ecc-systematic-review`); this plugin registers no `UserPromptSubmit` handler, so no code here exercises either spelling — verify before depending on it. Prompt and agent hooks receive the complete input through `$ARGUMENTS`.

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
Read more
Ships withsession-orchestrator

Give your agents a working rhythm. You type three commands: /session reads your repository, your open issues and the last session, proposes what to work on, and waits for your correction.

Get the whole plugin

Other skills on session-orchestrator.