Hooks

Hooks are user-defined shell scripts that run when events happen during the agent lifecycle.

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 largely Claude Code-compatible: same payload field names, same output forms, same exit codes

  • Crush ships with a builtin crush-hook skill to write, edit, and configure hooks; just tell Crush how to configure Crush

  • Crush supports 13 hook events covering the full agent lifecycle: tool use, session management, turn control, permissions, compaction, and error handling

  • 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 -f or cabal init

  • Rewrite tool input: turn node calls into deno, 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 set up our hook. Let's add the following to our project-level crushrc. 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/crushrc), use an absolute path instead.

# PreToolUse is a hook that fires before a tool is used# Matcher sets the tool we want to hook into# Command holds the path to the actual hook script# Name provides a friendly name to show in the TUI# Timeout ensures a reasonable cancellation window in case something goes wronghook add PreToolUse \  --matcher '^bash$' \  --command "./no-haskell.sh" \  --name "no-haskell" \  --timeout 30

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 2fi

That's basically it. For the full guide on how hooks work, however, read on: how hooks run, input & output, and examples.

Read this post in your terminal with glow:glow -p https://crush.charm.land/integrations/hooks/index.md

EOF