Skip to contentllms.txt

Crush — Hooks

File format
JSON
Discovery path
crush.json
Syllago install method
JSON merge
Symlink support
No
Config file
crush.json
Handler types
command

Hook events

Canonical EventNative NameCategory
before_tool_executePreToolUseTool

Features

How each feature converts to syllago's canonical format. See format conversion for what these statuses mean.

Fields

Native frontmatter or config fields this provider exposes. The first column is the provider's own field name; the canonical key column links to syllago's cross-provider equivalent.

Provider fieldConversionCanonical keySummary
contextTranslatedConversion type: TranslatedActively mapped to the target provider’s equivalent field during conversion.Learn more →context_injectionThe output envelope's context field accepts a string or an array of strings, appended to what the model sees for the current turn. Across multiple hooks, context values concatenate in config order; empty entries are dropped.
decisionTranslatedConversion type: TranslatedActively mapped to the target provider’s equivalent field during conversion.Learn more →decision_controlExit code 2 blocks the tool call (stderr becomes the deny reason); exit 49 halts the whole turn; other non-zero exit codes are non-blocking errors and the tool call proceeds. On exit 0, stdout is parsed as a JSON envelope whose decision field may be allow, deny, or null. allow is affirmative pre-approval (skips the permission prompt); null/omitted means no opinion. Across multiple hooks, deny > allow > null, composed in config order.
hooksTranslatedConversion type: TranslatedActively mapped to the target provider’s equivalent field during conversion.Learn more →—The hooks config key is an object keyed by event name, with each value an array of HookConfig objects — multiple hooks per event with per-tool matcher filtering. Event names are case insensitive and snake-caseable: PreToolUse, pretooluse, and pre_tool_use all work.
matcherTranslatedConversion type: TranslatedActively mapped to the target provider’s equivalent field during conversion.Learn more →matcher_patternsHookConfig has an optional matcher field: a regex tested against the tool name (e.g. bash, edit, mcp_github_create_pull_request). Omitted or empty matcher matches all tools.
nameTranslatedConversion type: TranslatedActively mapped to the target provider’s equivalent field during conversion.Learn more →—HookConfig has an optional name field: a friendly display name shown in the TUI, falling back to the command when omitted. Maps from the canonical hook manifest's name field on install.
updated_inputTranslatedConversion type: TranslatedActively mapped to the target provider’s equivalent field during conversion.Learn more →input_modificationThe exit-0 JSON envelope may include updated_input: a shallow-merge patch against tool_input. Included keys overwrite matching keys; omitted keys are preserved; nested objects are replaced wholesale. Patches from multiple hooks shallow-merge sequentially in config order (later hooks win on colliding keys) and are ignored if the final decision is deny or halt. Implemented as UpdatedInput in internal/hooks/hooks.go.
timeoutNot portableConversion type: Not portableUnique to this provider — can’t be carried across providers.Learn more →—Each HookConfig entry has a timeout field (integer, seconds, default 30). On timeout, Crush cancels the hook's context, waits a short grace period, then treats the result as a non-blocking error so the tool call proceeds — a timed-out hook cannot block.

Other features

Behaviors, conventions, and capabilities that aren't tied to a single named field — things like path-based activation, discovery rules, and lifecycle behavior.

FeatureConversionSummary
hook_scopesTranslatedConversion type: TranslatedActively mapped to the target provider’s equivalent field during conversion.Learn more →Hooks can be configured in crush.json (or .crush.json) at both the user-global (~/.config/crush/crush.json) and project level, with project-level hooks taking precedence. Global-config commands must use absolute paths or inline commands; relative paths resolve against the working directory.
json_io_protocolTranslatedConversion type: TranslatedActively mapped to the target provider’s equivalent field during conversion.Learn more →Stdin provides a JSON payload: event, session_id, cwd, plus tool_name and tool_input (the raw JSON the model sent) for PreToolUse. On exit 0 stdout is parsed as a JSON output envelope: version (defaults to 1), decision, halt, reason, context, updated_input. Simple hooks can skip JSON entirely and use CRUSH_* environment variables plus exit codes.
permission_controlTranslatedConversion type: TranslatedActively mapped to the target provider’s equivalent field during conversion.Learn more →Hook results apply before permission checks. An aggregated deny blocks the tool call before any permission prompt; an aggregated allow is affirmative pre-approval that skips the prompt entirely; silence (no decision) falls through to the normal permission flow.
Environment-variable inputNot portableConversion type: Not portableUnique to this provider — can’t be carried across providers.Learn more →Alongside stdin JSON, every hook receives environment variables: CRUSH=1, AGENT=crush, AI_AGENT=crush, CRUSH_EVENT, CRUSH_TOOL_NAME, CRUSH_SESSION_ID, CRUSH_CWD, CRUSH_PROJECT_DIR, and per-tool shortcuts CRUSH_TOOL_INPUT_COMMAND (bash) and CRUSH_TOOL_INPUT_FILE_PATH (file tools) — simple hooks never need to parse JSON.
Hooks fire before permission checksNot portableConversion type: Not portableUnique to this provider — can’t be carried across providers.Learn more →Hooks run before Crush's built-in permission gate: deny blocks the tool call before any prompt appears, and allow is affirmative pre-approval that skips the prompt — hooks can both veto and auto-approve independent of configured permissions.
Parallel execution, config-order compositionNot portableConversion type: Not portableUnique to this provider — can’t be carried across providers.Learn more →Matching hooks run in parallel for speed, deduplicated by identical command, and their results compose deterministically in config order: deny wins over allow, reasons and context concatenate in order, updated_input patches merge in order. Timing never determines the outcome.
Top-level-agent-only scopeNot portableConversion type: Not portableUnique to this provider — can’t be carried across providers.Learn more →PreToolUse fires only on the top-level agent's tool calls; sub-agents (the agent task tool, agentic_fetch) run without hook interception so one delegated turn doesn't fire a hook N times. The outer sub-agent tool call itself is hooked.
Turn halt (exit 49 / halt field)Not portableConversion type: Not portableUnique to this provider — can’t be carried across providers.Learn more →A hook can end the whole turn, not just block one tool call: exit code 49 (stderr becomes the halt reason) or halt: true in the JSON envelope. The agent doesn't respond further; the user takes over. Halt is sticky across multiple hooks.

Sources

Source
https://raw.githubusercontent.com/charmbracelet/crush/main/docs/hooks/README.md
https://raw.githubusercontent.com/charmbracelet/crush/main/schema.json
https://raw.githubusercontent.com/charmbracelet/crush/main/AGENTS.md
https://raw.githubusercontent.com/charmbracelet/crush/main/internal/skills/builtin/crush-config/SKILL.md