OSS · Bash CLI · Claude Code

hook-chain-lens

Claude Code hooks fire additively across four scopes. See what actually runs.

A read-only CLI that loads every hook definition Claude Code will merge for the current cwd (user, project, local, and each enabled plugin), resolves ${CLAUDE_PLUGIN_ROOT}, and prints the merged chain grouped by event with source scope, matcher, timeout, and origin file. Bash + jq, no writes, no runtime hooks.

View on GitHub What doctor catches →

Terminal demo: hook-chain-lens list prints the merged chain for the current cwd, showing PreCompact with two empty-matcher handlers, SessionStart, PostCompact and UserPromptSubmit, each annotated with source scope, matcher, timeout and origin file; then hook-chain-lens doctor flags [W1] "PreCompact" has 2 handlers with empty matcher.
list groups hooks by event with source scope and origin file. doctor turns the same data into pass / warn / error, with an exit code you can gate CI on.

What doctor catches

Eight rules over the merged chain. Errors return exit 1, so doctor works as a CI gate or pre-commit check for your Claude Code settings.

Code Severity What it catches
W1 warn Same event has two or more handlers with empty matcher — all of them fire on every trigger, order-preserved. Frequently intended; frequently not.
W2 warn Same event has the same matcher shared by two or more handlers — usually a copy-paste of a plugin hook that already existed.
W3 error A command references a file that does not exist after ${CLAUDE_PLUGIN_ROOT} expansion — the hook will fail every time it is triggered.
W4 warn The command file exists but is not readable or executable — permissions likely stripped by an install step.
W5 warn A timeout is unusually low (under one second) — likely a typo of the intended value.
I1 info A timeout is unusually high (over 300 seconds) — worth confirming the hook actually needs the budget.
W6 error An event name is not recognised by Claude Code — almost always a typo (PreToolUsage instead of PreToolUse, etc.).
W7 warn A matcher field is set on an event that does not support matchers — the field is dead, the handler fires on every trigger.

Install

Clone the repo and symlink the CLI into your PATH. Requires bash 4+, jq and coreutils. Linux and macOS supported.

git clone https://github.com/kenimo49/hook-chain-lens.git
ln -s "$(pwd)/hook-chain-lens/bin/hook-chain-lens" ~/.local/bin/hook-chain-lens

# requires: bash 4+, jq, coreutils

Usage

Three subcommands. Each supports --cwd for reading project and local scope from a different directory, and list supports --json for jq-driven pipelines.

hook-chain-lens list                       # merged chain, grouped by event
hook-chain-lens list --event PreToolUse    # filter to one event
hook-chain-lens list --cwd ~/other-repo    # read project/local scope from elsewhere
hook-chain-lens list --json | jq '.'       # machine-readable output

hook-chain-lens doctor                     # 8 rules; exit 1 on errors, 0 otherwise
hook-chain-lens events                     # every event Claude Code knows, grouped by matcher support

Four design calls

The interesting decisions are all about what the tool does not do.

  1. Read-only, always

    hook-chain-lens never edits settings.json, never installs a hook, and never executes one. It cannot silently disable something that broke, and it cannot alter what the next Claude Code run will do. Static analysis makes the tool safe to run mid-session and safe to keep in CI.

  2. Additive, not overriding

    Claude Code merges hooks additively — an event defined in user, project, local and plugin scopes runs all of them, in that order. list mirrors this exactly: no scope shadows another. This is the model that surprises people once four plugins are installed and each of them registers UserPromptSubmit.

  3. Enabled means installed AND opted in

    A plugin only contributes hooks if both installed_plugins.json lists it and settings.json sets enabledPlugins["<key>"] to true. Installed-but-disabled plugins are excluded, matching Claude Code's own runtime behaviour so the merged view is trustworthy.

  4. doctor exits non-zero on errors

    Warnings and info notes are shown but do not fail the run. Errors (W3 missing file, W6 unknown event) return exit 1, so hook-chain-lens doctor is a working pre-commit or CI gate for the settings files in your repo — a broken hook path never lands on main.

Related developer tools

All products →

← Back to products