Skip to main content
OrchestKit v6.7.1 — 67 skills, 38 agents, 77 hooks with Opus 4.6 support
OrchestKit

Unified Dispatchers

How one hooks.json entry fans out to many internal hooks across 7 events

Injects Global

OrchestKit consolidates 86 hooks into a small number of hooks.json entries using unified dispatchers --- single entry points that route to many internal hooks.

The Pattern

Without dispatchers, every hook would need its own entry in hooks.json, meaning Claude Code would spawn a separate Node.js process for each one. With 86 hooks, that would add significant latency to every tool call.

The unified dispatcher pattern solves this:

  1. One hooks.json entry registers a single command per event type
  2. One process spawn executes the dispatcher
  3. The dispatcher imports and runs multiple hook functions in-process
  4. Results are merged into a single response (context injection, input modification, or denial)

Dispatcher Registry

EventDispatcherInternal HooksBehavior
PreToolUse (Bash)unified-advisory-dispatcher8 hooksBudget-capped context injection (800 tokens)
PreToolUse (Write/Edit)unified-quality-dispatcher5 hooksContext + blocking
PreToolUse (Task)unified-agent-safety-dispatcher4 hooksContext + blocking
PostToolUseunified-dispatcherPost-execution analysis hooksSilent (fire-and-forget)
PostToolUse (Write)unified-write-quality-dispatcherPost-write quality analysisContext injection
Stopunified-stop-dispatcher23 hooksFire-and-forget via background worker
UserPromptSubmitunified-dispatcherPrompt analysis and context injectionContext injection
Lifecycle (SessionStart)unified-dispatcherSession initialization hooksSilent (fire-and-forget)
SubagentStopunified-dispatcherSubagent cleanup hooksSilent (fire-and-forget)
Notificationunified-dispatcherNotification processing hooksSilent (fire-and-forget)
Setupunified-dispatcherOne-time setup hooksSilent (fire-and-forget)

Execution Modes

Dispatchers use two execution strategies depending on the event type:

Synchronous (PreToolUse, UserPromptSubmit)

These dispatchers run hooks sequentially and return a merged result. They can:

  • Block the tool call (return continue: false)
  • Inject context (return additionalContext)
  • Modify input (return updatedInput)

The dispatcher stops early if any hook blocks.

Fire-and-Forget (Stop, PostToolUse, Lifecycle, SubagentStop, Notification, Setup)

These dispatchers are launched via background-worker.mjs as detached processes. The hooks.json entry returns immediately while the background worker:

  • Reads work from a temp file in .claude/hooks/pending/
  • Routes to the correct dispatcher via a registry
  • Runs all hooks via Promise.allSettled
  • Self-terminates after 5 minutes
  • Cleans up orphaned temp files older than 10 minutes

Security-critical hooks (dangerous-command-blocker, compound-command-validator, file-guard) are never part of a dispatcher. They run as standalone entries to guarantee they can block before any other logic.

Why Not One Giant Dispatcher?

Separating dispatchers by event type provides:

  • Isolation --- a crash in post-tool hooks cannot affect pre-tool blocking
  • Clarity --- each dispatcher file lists exactly which hooks it manages
  • Testability --- each dispatcher has its own test file with focused assertions
  • Budget control --- different events have different token budgets and merging strategies

Configuration

Dispatchers have no user-configurable options. The hook registry for each dispatcher is defined in its source file.

Edit on GitHub

Last updated on