clooks

March 25, 2026 · View on GitHub

Persistent hook runtime for Claude Code. Eliminate cold starts. Get observability.

npm License: MIT

Documentation

Performance

MetricWithout clooksWith clooksImprovement
Single hook invocation~34.6ms~0.31ms112x faster
Full session (120 invocations)~3,986ms~23ms99% time saved
5 parallel handlers~424ms~96ms4.4x faster

Benchmarked on Apple Silicon (M-series), Node v24.4.1. Run npm run bench to reproduce.

Installation

npm install -g @mauribadnights/clooks
clooks migrate    # migrates existing hooks, installs system service, installs clooks agent
clooks start

clooks migrate converts your settings.json command hooks into HTTP hooks backed by the daemon, auto-installs a system service (launchd/systemd) for auto-start and crash recovery, and installs the clooks expert agent (claude --agent clooks). Starting fresh instead? Use clooks init to create a blank manifest.

How It Works

Architecture

One persistent HTTP server replaces per-invocation process spawning. Claude Code POSTs hook events to the daemon, which dispatches to handlers defined in ~/.clooks/manifest.yaml. Handlers that fail 3 times consecutively are auto-disabled.

Quick Reference -- Commands

Daemon lifecycle:

CommandDescription
clooks startStart the daemon (-f foreground, --no-watch disable manifest watching)
clooks stopStop the daemon
clooks statusShow daemon status, uptime, handler count, service state
clooks ensure-runningStart daemon if not running (used internally by SessionStart hook)

Observability:

CommandDescription
clooks statsInteractive TUI for execution metrics (-t for plain text)
clooks costsLLM token usage and cost breakdown
clooks doctorRun diagnostic health checks

Configuration:

CommandDescription
clooks migrateConvert settings.json command hooks to HTTP hooks, install service + agent
clooks restoreRestore original settings.json from backup
clooks syncSync settings.json with manifest (add missing HTTP hook entries)
clooks initCreate default config directory and example manifest
clooks updateUpdate clooks to latest version and refresh agent
clooks rotate-tokenGenerate new auth token, update manifest + settings.json, hot-reload daemon

Plugins:

CommandDescription
clooks add <path>Install a plugin from a local directory
clooks remove <name>Uninstall a plugin and its contributed handlers
clooks pluginsList installed plugins and their handlers

System service:

CommandDescription
clooks service installInstall as system service (auto-start on login, auto-restart on crash)
clooks service uninstallRemove system service
clooks service statusShow service status

Configuration -- Manifest

Handlers are defined in ~/.clooks/manifest.yaml:

# Pre-fetch shared context once per event, available as $VARIABLES in LLM prompts
prefetch:
  - transcript        # last 50KB of session transcript
  - git_status        # git status --porcelain
  - git_diff          # git diff --stat (max 20KB)

handlers:
  PreToolUse:
    # Script handler -- spawns a shell command
    - id: safety-guard
      type: script
      command: node ~/hooks/guard.js
      filter: "Bash|Execute|!Read"      # OR logic, ! negates
      project: "*/my-project/*"         # only fire in matching cwd
      timeout: 3000
      enabled: true

    # LLM handler -- calls Anthropic Messages API (default backend)
    - id: code-review
      type: llm
      model: claude-haiku-4-5
      prompt: "Review this $TOOL_NAME call: $ARGUMENTS"
      batchGroup: analysis              # batched with other handlers in same group
      maxTokens: 512
      temperature: 0.5
      depends: [safety-guard]           # waits for safety-guard to complete first

    # Another LLM handler in the same batch group -- one API call for both
    - id: security-check
      type: llm
      model: claude-haiku-4-5
      prompt: "Check for security issues in $TOOL_NAME: $ARGUMENTS"
      batchGroup: analysis
      agent: "builder"                  # only fire in builder agent sessions

    # LLM handler -- spawns Claude Code CLI with an agent
    - id: agent-review
      type: llm
      backend: claude-code              # spawn claude CLI instead of API call
      llmAgent: security-reviewer       # --agent flag
      prompt: "Audit this change: $TOOL_NAME $ARGUMENTS"
      filter: "Bash|Write"

  UserPromptSubmit:
    # Inline handler -- imports a JS module in-process (no subprocess)
    - id: prompt-logger
      type: inline
      module: ~/.clooks/handlers/logger.js
      async: true                       # fire-and-forget, doesn't block response
      sessionIsolation: true            # reset state on SessionStart

  Stop:
    - id: session-summary
      type: llm
      model: claude-haiku-4-5
      prompt: "Summarize this session:\n$TRANSCRIPT\n\nGit changes:\n$GIT_DIFF"

settings:
  port: 7890
  logLevel: info                        # debug | info | warn | error
  authToken: your-token-here            # auto-generated by migrate/init
  # anthropicApiKey: sk-...             # or set ANTHROPIC_API_KEY env var

Handler Types

TypeOverheadLanguageUse case
script~5-35ms (subprocess)Any (shell command)Existing scripts, non-JS tools
inline<1ms (in-process)JavaScript/TypeScriptPerformance-critical handlers
llmNetwork-boundPrompt templateAI-powered analysis, review, summarization

script -- runs sh -c "command", pipes hook JSON to stdin, reads JSON from stdout.

inline -- imports a JS module and calls its default export. No subprocess overhead.

llm -- AI-powered analysis with $VARIABLE interpolation. Two backends: api (default, Anthropic API with batching and cost tracking) and claude-code (CLI spawn with agent support).

Handler Fields Reference

FieldTypeDefaultApplies toDescription
idstringrequiredallUnique handler identifier
typestringrequiredallscript, inline, or llm
commandstringrequiredscriptShell command to execute
modulestringrequiredinlinePath to JS module with default export
modelstringrequiredllmclaude-haiku-4-5, claude-sonnet-4-6, or claude-opus-4-6. Required for api backend, optional for claude-code.
promptstringrequiredllmPrompt template with $VARIABLE interpolation
backendstringapillmapi (Anthropic API) or claude-code (CLI spawn)
llmAgentstring--llmAgent name for claude-code backend (--agent flag)
filterstring--allKeyword filter (see Filtering)
projectstring--allGlob pattern matched against cwd
agentstring--allOnly fire when session agent matches
asyncbooleanfalseallFire-and-forget, don't block response
dependsstring[]--allHandler IDs to wait for before executing
sessionIsolationbooleanfalseallReset handler state on SessionStart
batchGroupstring--llmGroup ID for batching into one API call (api backend only)
maxTokensnumber1024llmMaximum output tokens
temperaturenumber1.0llmSampling temperature
timeoutnumber5000/30000allTimeout in ms (5s default, 30s for llm)
enabledbooleantrueallDisable without removing

Scoped Execution

Handlers can be scoped to specific projects or agents:

- id: driffusion-lint
  type: script
  command: node ~/hooks/lint.js
  project: "*/Driffusion/*"     # only fires when cwd matches this glob

- id: builder-guard
  type: inline
  module: ~/hooks/guard.js
  agent: "builder"              # only fires in builder agent sessions

Both fields are optional. When omitted, the handler fires for all projects/agents.

Filtering

The filter field skips handlers based on keywords matched against the full JSON-serialized hook input (case-insensitive):

filter: "word1|word2"       # run if input contains word1 OR word2
filter: "!word"             # run unless input contains word
filter: "word1|!word2"     # run if word1 present AND word2 absent
- id: bash-guard
  type: script
  command: node ~/hooks/guard.js
  filter: "Bash|Execute|!Read"   # runs for Bash/Execute tools, never for Read

LLM Handlers

LLM handlers support two backends: api (default, Anthropic Messages API) and claude-code (spawns claude CLI).

API backend setup:

npm install @anthropic-ai/sdk    # peer dependency, required only for api backend
export ANTHROPIC_API_KEY=sk-...  # or set in manifest: settings.anthropicApiKey

Claude Code backend requires no API key or SDK — just the claude CLI installed and authenticated. Supports the llmAgent field for running prompts with a specific agent (--agent).

Prompt template variables:

VariableSourceDescription
$TRANSCRIPTPre-fetched transcript fileLast 50KB of session transcript
$GIT_STATUSgit status --porcelainCurrent working tree status
$GIT_DIFFgit diff --statChanged files summary (max 20KB)
$ARGUMENTShook_input.tool_inputJSON-stringified tool arguments
$TOOL_NAMEhook_input.tool_nameName of the tool being called
$PROMPThook_input.promptUser's prompt (UserPromptSubmit only)
$CWDhook_input.cwdCurrent working directory

$TRANSCRIPT, $GIT_STATUS, and $GIT_DIFF require the corresponding key in prefetch. The others are always available from the hook input.

Batching (API backend only): Handlers sharing a batchGroup on the same event are combined into a single API call. Three Haiku calls become one, saving ~2/3 of input token cost and eliminating two round-trips. Batch groups are scoped per session to prevent cross-session contamination. Claude Code backend handlers always execute individually.

Async Handlers

Handlers with async: true execute fire-and-forget -- they run in the background and do not block Claude's response. Use this for logging, analytics, or any work that does not need to inject context back into the session.

- id: session-tracker
  type: inline
  module: ~/hooks/tracker.js
  async: true

Dependency Resolution

Handlers can declare dependencies with depends. clooks resolves them into topological execution waves -- handlers in the same wave run in parallel, waves execute sequentially.

- id: context-loader
  type: inline
  module: ~/hooks/context.js

- id: security-check
  type: llm
  model: claude-haiku-4-5
  prompt: "Check $TOOL_NAME given context: $CONTEXT"
  depends: [context-loader]    # runs in wave 2, after context-loader completes in wave 1

Pre-fetch

Fetch shared context once per hook event and make it available to all handlers via $VARIABLE interpolation in LLM prompts.

KeySourceMax sizeDescription
transcripttranscript_path file50KB (tail)Session conversation history
git_statusgit status --porcelainunboundedWorking tree status
git_diffgit diff --stat20KBChanged files summary

Pre-fetched data is cached for the duration of a single event dispatch. Errors on individual keys are silently caught -- a failed git_status does not prevent transcript from loading.

Observability

Execution metrics -- clooks stats launches an interactive TUI by default. Use -t for plain text (also auto-selected when piped):

$ clooks stats -t

Event               Fires     Errors    Avg (ms)    Min (ms)    Max (ms)
------------------------------------------------------------------------
PreToolUse          47        0         1.2         0.8         3.1
Stop                12        0         2.4         1.1         5.6
UserPromptSubmit    12        1         1.8         0.9         4.2

Total fires: 71 | Total errors: 1 | Spawns saved: ~71

Diagnostics -- clooks doctor runs health checks on daemon, port, manifest, settings, and handler state:

$ clooks doctor

[pass] Daemon is running (PID 44721, uptime 2h 13m)
[pass] Port 7890 is responding
[pass] Manifest loaded: 4 handlers across 3 events
[pass] settings.json has HTTP hooks pointing to clooks
[pass] No handlers in circuit-breaker state
[warn] 1 handler error in last 24h (session-logger on Stop)

Cost tracking -- clooks costs shows LLM token usage and spend per handler and model:

$ clooks costs

LLM Cost Summary
  Total: \$0.0142 (4,280 tokens)

  By Model:
    claude-haiku-4-5       \$0.0142 (4,280 tokens)

  By Handler:
    code-review            \$0.0089 (12 calls, avg 178 tokens)
    security-check         \$0.0053 (12 calls, avg 178 tokens)

Built-in pricing (per million tokens): Haiku ($0.80 / $4.00), Sonnet ($3.00 / $15.00), Opus ($15.00 / $75.00). Costs persist to ~/.clooks/costs.jsonl.

System Service

clooks service install creates a platform-native service (launchd on macOS, systemd on Linux) that starts the daemon on login and restarts it on crash. clooks migrate and clooks init install the service automatically. Use clooks service status to check and clooks service uninstall to remove.

Plugin Development

Plugins package reusable sets of handlers. A plugin is any directory with a clooks-plugin.yaml:

# clooks-plugin.yaml
name: my-security-suite
version: 1.0.0
description: Security guards for tool calls
author: your-name

handlers:
  PreToolUse:
    - id: bash-guard
      type: inline
      module: $PLUGIN_DIR/handlers/bash-guard.js    # $PLUGIN_DIR resolves to plugin install path
      timeout: 3000
    - id: file-guard
      type: inline
      module: $PLUGIN_DIR/handlers/file-guard.js
      timeout: 2000

prefetch:
  - git_status

extras:
  skills: [security-audit]       # skill names this plugin provides
  agents: [security-reviewer]    # agent names this plugin provides
  readme: README.md              # path to plugin README (relative to plugin dir)

Installing and managing plugins:

clooks add ./my-security-suite     # install from local path
clooks remove my-security-suite    # uninstall
clooks plugins                     # list installed plugins + handlers

Handler IDs are automatically namespaced to the plugin (my-security-suite/bash-guard) to avoid collisions with user-defined handlers or other plugins.

Expert Agent

clooks ships with an expert agent that understands the full architecture, configuration, and troubleshooting workflow. It is auto-installed and auto-updated by clooks migrate, clooks init, and clooks update. Invoke it with claude --agent clooks.

Short-Circuit Chains

When a PreToolUse handler returns a deny decision, clooks automatically skips the corresponding PostToolUse handlers for that tool call. Deny results are cached with a 30-second TTL, so repeated calls to the same tool with the same arguments short-circuit without re-evaluating handlers.

Configuration Reference

ItemPath / Value
Port7890 (default)
Config directory~/.clooks/
Manifest~/.clooks/manifest.yaml
Metrics~/.clooks/metrics.jsonl
Costs~/.clooks/costs.jsonl
Daemon log~/.clooks/daemon.log
PID file~/.clooks/daemon.pid
Plugins directory~/.clooks/plugins/

Contributing

Setup

git clone https://github.com/mauribadnights/clooks.git
cd clooks
npm install

Codebase layout

src/
  cli.ts          Command definitions (commander)
  server.ts       HTTP daemon — hook routing, auth, session management
  handlers.ts     Handler execution engine (script, inline, LLM)
  manifest.ts     Manifest loading and validation
  metrics.ts      Metrics collection and aggregation
  tui.ts          Interactive terminal dashboard (ANSI-based)
  llm.ts          LLM execution (Anthropic API + Claude Code CLI) and batching
  filter.ts       Keyword filter engine
  prefetch.ts     Pre-fetch context (transcript, git status/diff)
  plugin.ts       Plugin install/remove/list
  ...

tests/            Mirrors src/ — one test file per module
benchmarks/       Performance benchmarks
docs/             Architecture diagram and assets
hooks/            Built-in hook scripts
agents/           Built-in agent definitions

Development workflow

npm run build       # Compile TypeScript to dist/
npm test            # Run all tests (vitest)
npm run test:watch  # Watch mode
npm run bench       # Run performance benchmarks
npx tsc --noEmit    # Type-check without emitting

Pull request guidelines

  1. Fork the repo and create a feature branch from main
  2. Write tests for new functionality — tests are required for all PRs
  3. Ensure npm test passes and npx tsc --noEmit reports zero errors
  4. Write a clear PR description explaining what changed and why
  5. Keep PRs focused — one feature or fix per PR

Bug reports and feature requests

Open an issue at github.com/mauribadnights/clooks/issues with reproduction steps for bugs or a use-case description for features.

License

MIT