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. No schema, no types, no imports — every hook is a one-liner you quote by hand.
All native hooks run in parallel. No ordering, no pipeline, no way for one hook to modify input before another sees it. (Open issue claude-code#15897.)
A hook that works on your machine lives in your settings. A teammate clones the repo and gets nothing — or a different version.
The best hooks are gists linked in Discord threads. There is no registry, no pinning, no lockfile.
On the left, a Claude Code session. On the right, the hook file.
Each file exports one or more ClooksHook objects. Every event you handle is a method with a typed context and a typed return. Config is validated with Zod and merged over your defaults. Hover a row below to see where it lives in the source.
A hook's meta.config is its public dial. Here's no-destructive-git — thirteen toggles and a rule array — reshaped in three repos without touching a line of source.
.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 at the same SHAs.
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.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 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.
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.