2.0 KiB
hooklib
A small library of composable Claude Code hooks. Each hook is a drop-in script; a shared router dispatches one event to all of them. Adding a hook is just dropping an executable script into the matching directory — no wiring.
Layout
hooks/
hooks.json # registers the router per event (currently PreToolUse:Bash)
dispatch.sh # router: feeds the event JSON to every hooks/<event>.d/*.sh
pretooluse.d/
10-nix-deploy-output.sh
How dispatch works
dispatch.sh <Event> reads the event JSON from stdin once and replays it to
each script in hooks/<event>.d/ (lexical order). A sub-hook that wants to act
prints a hook JSON response to stdout and exits 0; the first sub-hook to
emit non-empty output wins and the router forwards it verbatim. If nobody
speaks up, the router is silent (Claude Code treats that as "allow").
Scripts are numbered (10-, 20-, …) to make ordering explicit.
Hooks
nix-deploy-output (PreToolUse / Bash)
Problem: nix run .#deploy (deploy-rs) shows empty output in Claude
Code. deploy-rs and nix render progress as an ANSI spinner on a TTY and log to
stderr; captured non-interactively, that collapses to a blank.
Fix: the command must fold stderr into stdout and defeat TTY detection so the tools emit plain, line-buffered logs. The canonical form is:
nix run .#deploy 2>&1 | cat
The hook inspects any nix run .#deploy… command. If it is already in the
output-safe form (2>&1 and a pipe into cat/tee/less) it passes
through untouched. Otherwise it denies the call and returns the corrected
command (preserving any -- flags / host target) for Claude to run instead.
Adding another hook
Drop hooks/<event>.d/NN-name.sh (executable) that reads the event JSON on
stdin and prints a hook response only when it wants to intervene. If the event
isn't PreToolUse, add it to hooks/hooks.json pointing at
dispatch.sh <Event>.
Install (local dev)
claude plugin install hooklib@oleks-local