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:
- Registers lightweight bus handlers on exactly the Codumentor hooks that appear in your rule set (no dead handlers).
- Matches the Claude Code
matcher:regex against the tool name on per-tool events (PreToolUse,PostToolUse,ToolProgress). - Runs all matching actions concurrently via
asyncio.gather; the firstblockdecision wins. - Translates a
blockdecision into the appropriate Codumentor action for each event (short-circuit, veto-tool, insert-step, or no-op for observation-only events).
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):
| Source | When 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 files | Opt-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
| Event | Codumentor bus hook | Block behaviour |
|---|---|---|
SessionStart | onContextReady (once) | no-op (logged) |
SessionEnd | onSessionEvict | no-op (logged) |
UserPromptSubmit | onInputReceived | short-circuit — reason becomes the assistant's reply |
PreToolUse | onToolInvokeStart | veto-tool — synthetic blocked: <reason> result feeds back to the LLM |
PostToolUse | onToolInvokeEnd | insert a system message "Hook blocked: <reason>" visible on the next turn |
Stop | onBeforeSendResponse | short-circuit — reason replaces outgoing assistant text |
Codumentor-unique
| Event | Codumentor bus hook | Typical use |
|---|---|---|
PreLLMCall | onLLMCallStart | inspect model/params before the call |
PostLLMCall | onLLMCallEnd | log completions, token usage |
LLMError | onLLMError | external alerting on model failure |
PromptAssemble | onPromptAssemble | inject retrieved context or mutate tool list |
TurnInterrupted | onTurnInterrupted | cleanup when user cancels the turn |
ToolProgress | onToolProgress | stream long-running tool output to external systems |
ResponsePersist | onResponsePersist | audit / 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
| Code | Meaning |
|---|---|
0 | allow (JSON on stdout is still honoured when present) |
2 | block (stderr becomes the reason if no JSON is supplied) |
| other | warn-only; stderr logged via plugins_logger |
Environment
CLAUDE_PROJECT_DIR,CODUMENTOR_PROJECT_DIR— workspace rootCLAUDE_HOOK_EVENT,CODUMENTOR_HOOK_EVENT— event nameCLAUDE_SESSION_ID— session identifier
$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.