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.
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.
-
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.
-
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.
-
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.
-
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
- private-lint Git-hook gate that blocks personal and private names (family names, personal emails, client domains) before commit and push. Pure Bash; detection patterns stay machine-local, never in any repo, and a full-history audit covers the moment before you flip a repository public.
- quake-lens Earthquake statistics CLI + MCP server in pure-stdlib Python: Gutenberg-Richter b-value (Aki MLE) and Omori-Utsu aftershock decay (Ogata MLE) from public catalogs.
- rhythm-lens Measures the rhythm of Japanese, English and Portuguese Markdown (sentence-length burstiness, paragraph structure) against measured human/AI distributions from two published papers. A writing feedback instrument, not an AI detector.
- claude-shift Multi-account Claude Code manager. Switch the active login, sync the two-file auth Claude Code actually reads (~/.claude/.credentials.json + ~/.claude.json), and watch 5-hour and weekly usage across all accounts. CLI + local API + Chrome extension + browser Web UI + Tauri v2 desktop.