Hooks
NOTE
This document was designed for both humans and agents.
Hooks are user-defined shell scripts that run when various events happen during the agent lifecycle, allowing you to both build on top of Crush, customize its behavior, and exert deterministic control over an agent's wily behavior.
Hooks are just shell commands, and were designed to be both simple and future forward.
Hot Hook Facts
- Hooks are just shell commands
- Hooks can be written in any language because they're just executables: Bash, Python, Node, Rust, Haskell, whatever
- Hooks are Claude Code-compatible
- Crush ships with a builtin
crush-hookskill write, edit, and configure hooks; just tell Crush how to configure Crush - Crush currently supports just one hook,
PreToolUse, with plans to support the full gamut; please let us know which hooks you'd like to see next - Hooks run in parallel for speed, but their results compose in config order for determinism
Some things you can do with hooks:
- Block "dangerous" commands: no more
git push -forcabal init - Rewrite tool input: turn
nodecalls infodeno, scrub secrets from commands, rewrite all mentions of "Haskell" into "Haskell, The Best Language", and so on - Inject context: add notes to the model's context whenever certain tools are called. For example: "remember to run gofumpt after editing Go files"
- Auto-approve tools: skip the permission prompt for bash commands that you know are safe
- Log certain tool calls
…And lots more. Show us what you're building!
Baby's First Hook
Let's just dive into it and make a simple hook. This particular hook will disallow the use of Haskell (but we love you, Simon Peyton Jones).
Config
The first thing we need to do is hook up our hook. Let's add the following to our project-level crush.json. Relative paths like ./no-haskell.sh work here because the project root is your working directory. If you're configuring a global hook (~/.config/crush/crush.json), use an absolute path instead.
{
// As expected, hooks go in a "hooks" object.
"hooks": {
// PreToolUse is an event that fires before a tool is used.
"PreToolUse": [
{
// What tool do we want to hook into? In this case, Bash, because it
// runs the stuff we wanna block.
"matcher": "^bash$",
// The path to our actual hook script.
"command": "./no-haskell.sh",
},
],
},
}Now, let's make our no-haskell.sh hook script.
#!/usr/bin/env bash
# Disallow ghc, cabal, and stack. Pipe the bash command output
# ($CRUSH_TOOL_INPUT_COMMAND) to grep and match on a regexp.
if echo "$CRUSH_TOOL_INPUT_COMMAND" | grep -qE '(^| )((ghc|cabal|stack)(\.exe)?)( |$)'; then
# Someone is trying to use Haskell. Let's send a message back to the model
# and user explaining why we're blocking this. Note that we send all feedback
# like this to stderr.
echo "No Haskell allowed, kiddo." >&2
# Now, block the tool call by exiting with code 2.
exit 2
fiThat's basically it. For the full guide on how hooks work, however, read on: how hooks run, input & output, and examples.
