Write hooks as small TypeScript files. Clooks runs them when Claude Code edits files or runs commands, and blocks the action if a hook crashes.
rm -rf ~/ crashed.Somebody's agent ran rm -rf tests/ patches/ plan/ ~/ — the trailing ~/ wiped the Mac. A guard hook was meant to catch it, but threw an exception and exited with a non-2 code. Claude Code treats anything other than exit 2 as success, so the command ran. In Clooks, a crashed hook blocks the action by default.
Claude Code only blocks on exit code 2. A guard hook that crashes — a typo, a missing dep — doesn't prevent the action. The action runs as if the hook never ran.
Native hooks are bash strings inside .claude/settings.json. Every hook is a one-liner you quote by hand or write a new bash script for.
All native hooks run in parallel. No ordering, no pipeline, no way for one hook to modify input before another sees it.
A hook you wrote for one repo lives in that repo's settings file. Copying it to the next project means re-pasting bash strings and re-committing script files. And in plugins, you might not want every hook enabled.
The best hooks are gists linked in Discord threads. Sharing only works through Claude Marketplace, which can open up update injection vectors.
On the left, a Claude Code session. On the right, the hook file.
Each file exports one ClooksHook object, which can handle one or more events. Every event you handle is a method with a typed context and a typed return. Hover a row below to see where it lives in the source.
A hook's meta.config is its public interface. Here's no-destructive-git configured to suit each repositories use case without having to write three separate hooks.
Recorded from a Claude Code session.
no-rm-rf hook returns { result: "block", reason }. Claude reads the reason, stops, and surfaces it back to the user..clooks/.clooks init writes a self-contained folder. Only the entrypoint script is registered into .claude/settings.json. A teammate cloning the repo gets the same hooks as they're checked in.
Claude Code runs every matching hook in parallel. Nothing stops a slow hook when a fast one already said no — and any short-circuit logic has to be duplicated into every hook that needs it.
Clooks parallelizes and orders hooks. Expensive checks only run when the cheap ones passed — no duplicated short-circuit logic in every hook.
Each layer adds its own hooks and can override the ones beneath. Personal defaults in home, team rules in the repo, and a gitignored local file for the exceptions only you need.
js-package-manager-guard stops Claude from reaching for the wrong package manager. The block reason tells Claude what to do instead — it self-corrects on the next tool call.
Each ends the same way: a committed .clooks/ directory and the Clooks binary on your PATH.
Install through the Claude Code plugin system. The plugin is a bootstrap — it registers a SessionStart hook that tells Claude to run /clooks:setup, which downloads the binary and runs clooks init in your project.
clooks init --global to register hooks under ~/.clooks/ for every Claude Code session.Clooks uses the plugin system for distribution. The runtime itself lives outside the plugin sandbox.
A plugin can quietly download and install binaries on your machine, then wire them into your agent. You shouldn't be surprised by a binary landing on your machine just because you cloned a repo or installed a plugin. Clooks keeps that surface visible — the bash entrypoint sits in .claude/settings.json, the .clooks/ directory is committed alongside your code, and pulling the runtime binary is an explicit step, not something the plugin does behind your back.