Codumentor logo Codumentor

Claude Code Hooks Plugin

The Claude Code Hooks plugin runs external shell commands or HTTP
endpoints on Codumentor agent lifecycle events. It is wire-compatible
with Anthropic's Claude Code hooks — the
same JSON-in/JSON-out protocol, the same event names, the same
settings.json schema — so existing hook scripts can be reused without
modification.

Alongside the six Claude Code events, the plugin exposes seven
Codumentor-unique lifecycle events for power users who want to observe
or integrate with LLM calls, prompt assembly, tool progress, and
persistence.

Overview

The plugin:

Enable

plugins:
  - module: codumentor.plugins.claude_code_hooks
    class: ClaudeCodeHooksPlugin
    priority: 5
    args:
      hooks:
        PreToolUse:
          - matcher: "Bash"
            hooks:
              - type: command
                command: "$CODUMENTOR_PROJECT_DIR/.codumentor/hooks/block-rm.sh"
                timeout: 10
        UserPromptSubmit:
          - hooks:
              - type: command
                command: ".codumentor/hooks/audit.sh"

The hooks: block is a drop-in Claude Code settings.json snippet.
YAML parses JSON as a subset, so you can paste existing Claude Code
rules unchanged.

Configuration Sources

Three inputs are merged in this order (later entries appended):

SourceWhen to use
args.hooks (inline)Small, project-specific rule sets
args.hooks_file (YAML/JSON)Sharing hook sets across configs; extends-friendly
.claude/settings.json filesOpt-in reuse of existing Claude Code setups
args:
  hooks_file: "hooks.yaml"                 # resolved vs. codumentor.yaml's dir
  discover_claude_settings: true           # default false
  claude_settings_paths:
    - ".claude/settings.json"
    - ".claude/settings.local.json"
    - "~/.claude/settings.json"

Paths support ~ expansion and ${VAR} interpolation. Invalid rules
are logged and dropped rather than crashing plugin load.

Events

Claude Code parity

EventCodumentor bus hookBlock behaviour
SessionStartonContextReady (once)no-op (logged)
SessionEndonSessionEvictno-op (logged)
UserPromptSubmitonInputReceivedshort-circuit — reason becomes the assistant's reply
PreToolUseonToolInvokeStartveto-tool — synthetic blocked: <reason> result feeds back to the LLM
PostToolUseonToolInvokeEndinsert a system message "Hook blocked: <reason>" visible on the next turn
StoponBeforeSendResponseshort-circuit — reason replaces outgoing assistant text

Codumentor-unique

EventCodumentor bus hookTypical use
PreLLMCallonLLMCallStartinspect model/params before the call
PostLLMCallonLLMCallEndlog completions, token usage
LLMErroronLLMErrorexternal alerting on model failure
PromptAssembleonPromptAssembleinject retrieved context or mutate tool list
TurnInterruptedonTurnInterruptedcleanup when user cancels the turn
ToolProgressonToolProgressstream long-running tool output to external systems
ResponsePersistonResponsePersistaudit / analytics on each persisted assistant turn

Blocking is a no-op on Codumentor-unique events; the reason is logged
so mis-configurations surface in diagnostics.

Subprocess Protocol (type: command)

stdin — a single JSON object:

{
  "session_id": "abc123",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {"command": "rm -rf /"},
  "cwd": "/home/user/project",
  "payload": { "...": "raw plugin-bus payload for the event" }
}

stdout — optional JSON; plain text is treated as reason:

{
  "decision": "block",
  "reason": "Destructive rm blocked by policy",
  "hookSpecificOutput": {"hookEventName": "PreToolUse"}
}

Exit codes

CodeMeaning
0allow (JSON on stdout is still honoured when present)
2block (stderr becomes the reason if no JSON is supplied)
otherwarn-only; stderr logged via plugins_logger

Environment

$CLAUDE_PROJECT_DIR / ${CLAUDE_PROJECT_DIR} and the Codumentor alias
are expanded inside the command string before spawning.

Timeouts — per-hook timeout: <seconds>. Timeouts are treated as
warn-only (no block) and logged.

Concurrency — all matching hooks for one event run concurrently.
First block wins; other outputs are still captured in the logs.

HTTP Hook (type: http)

PreToolUse:
  - matcher: "Bash"
    hooks:
      - type: http
        url: "https://audit.internal/codumentor-hook"
        method: POST
        headers: {Authorization: "Bearer ${AUDIT_TOKEN}"}
        timeout: 5

Request body matches the subprocess stdin. Response body (if JSON) is
parsed with the same decision / reason contract. Non-2xx responses
behave like a non-zero exit code — warn-only unless decision is set
explicitly.

Matchers and if: DSL

matcher: is a regex applied to the tool name; it fires on per-tool
events only.

A tiny if: DSL is accepted for forward-compat: ToolName(arg-glob)
matches the tool name and a glob against the string form of the tool
arguments. Anything richer is logged and ignored so mis-uses surface
immediately rather than silently passing or failing.

Priority

The plugin defaults to priority 5 — very early in the pipeline — so
PreToolUse and UserPromptSubmit handlers observe raw payloads
before other plugins (e.g. retrieval, memory) mutate them. Override
via priority: in the plugin entry if you want last-word observation.