Crush AI Coding Assistant

repository·main·Indexed 12 days ago

https://github.com/charmbracelet/crush

A terminal-based AI coding assistant that integrates tools, code, and workflows with various LLMs. Crush is extensible via Model Context Protocol (MCP) servers and enhanced by Language Server Protocols (LSPs). It features a Bash-based configuration system via `crushrc` for managing providers, models, event hooks, and tool permissions.

Tokens
30K
Snippets
103
Records
143
Agent score
97%

What's inside Crush

  1. Understand Crush configuration precedence and state locations

    main

    Crush merges configuration from multiple sources. Project-level settings override global settings. If a directory contains both a crushrc and a legacy .crush.json file, they are merged and Crush will log a warning.

    Configuration Precedence

    PriorityUnix-likeWindows
    1./.crushrc.\.crushrc
    2./crushrc.\crushrc
    3$XDG_CONFIG_HOME/crush/crushrc%XDG_CONFIG_HOME%\crush\crushrc

    State and Data Directories

    Crush uses specific directories for machine-owned JSON state and application state. Do not edit these by hand.

    TypeUnix-likeWindows
    State Data~/.local/share/crush%LOCALAPPDATA%\crush
    App State$XDG_DATA_HOME/crush%LOCALAPPDATA%\crush
  2. Planned: Use `UserPromptSubmit` event for prompt manipulation

    main

    The UserPromptSubmit event is a planned hook event that fires after a user submits a prompt but before it reaches the LLM. This allows hooks to modify, augment, or gate user input.

    Use Cases

    • Context Injection: Prepending project context (e.g., current branch, last commit).
    • Reference Injection: Using context_files to point the agent to relevant files.
    • Redaction: Removing secrets from the prompt before transmission.
    • Policy Enforcement: Denying prompts that violate security policies.
    • Expansion: Expanding shorthand (e.g., @TODO into a full instruction).

    Input/Output Schema

    Input (Stdin):

    {
      "event": "UserPromptSubmit",
      "session_id": "...",
      "cwd": "/home/user/project",
      "prompt": "fix the login flow",
      "attachments": ["screenshot.png"]
    }

    Output (Stdout):

    {
      "decision": "allow",
      "reason": "includes a production secret",
      "context": "Current branch: feat/login",
      "updated_prompt": "fix the login flow\n\n(from @TODO on line 42)"
    }

    Aggregation Rules

    • halt: Sticky; stops the entire turn before the LLM is called.
    • context: Concatenates across hooks in config order.
    • updated_prompt: This is a full replacement, not a merge. If multiple hooks emit updated_prompt, the last one in configuration order wins.
    • decision: "deny": Blocks the submission. The user sees the provided reason, and the turn never reaches the LLM.
  3. Manage provider auto-updates

    main

    By default, Crush automatically updates its list of providers and models from the Catwalk database.

    Disable automatic updates

    If you are in an air-gapped environment or have restricted internet access, you can disable this feature using either a crushrc option or an environment variable.

    Manually update providers

    You can manually trigger updates using the crush update-providers command, allowing you to pull from remote URLs, local files, or reset to the version embedded at build time.

    # Disable via crushrc
    option provider-auto-update false
    
    # Disable via environment variable
    export CRUSH_DISABLE_PROVIDER_AUTO_UPDATE=1
    
    # Update providers remotely from Catwalk
    crush update-providers
    
    # Update providers from a custom Catwalk base URL
    crush update-providers https://example.com/
    
    # Update providers from a local file
    crush update-providers /path/to/local-providers.json
    
    # Reset providers to the embedded version
    crush update-providers embedded
  4. Real-time configuration via the bash tool (Planned)

    main

    Concept: Session-only Configuration

    Note: This feature is currently planned and not yet implemented.

    Crush is moving toward a mental model similar to a shell and its .bashrc. Currently, crushrc is only read at startup. The planned feature allows the agent's bash tool to execute configuration commands that modify the running session only.

    Key Principles

    • Ephemeral Changes: Running a config command via bash changes the current session immediately but does not persist to disk. To make a change permanent, you must manually edit your crushrc.
    • No Auto-Persistence: The bash tool will never write to your configuration files. This prevents accidental or unintended permanent changes to your setup.

    Example Usage (Proposed)

    When implemented, you will be able to run commands like these directly within a session via the bash tool:

    # Switch models for the current session only
    model small anthropic/claude-haiku-4-20250514
    
    # Quiet the UI for the current session
    option progress false
    
    # Grant permission for a specific tool for the current session
    permissions allow grep
  5. Understand the Hook Execution Model

    main

    Crush uses an embedded POSIX shell (mvdan.cc/sh) for hook execution. This provides a consistent experience across platforms.

    Platform Behavior:

    • Windows: Supports inline shell (echo, jq, grep), shebang-less .sh scripts, inline PowerShell, and .exe invocations without requiring WSL or Git Bash.
    • Shebangs: Scripts with a #! shebang dispatch to the named interpreter via os/exec. If an absolute path in a shebang is missing (common on Windows), Crush falls back to a PATH lookup of the base name. If the interpreter is not on PATH, the hook fails as a non-blocking warning.
    • PowerShell: .ps1 files are not auto-dispatched. Use powershell -File ./script.ps1 explicitly.

    Lifecycle & Safety:

    • Timeout: If a hook exceeds its timeout, Crush cancels the context and waits ~1s for the interpreter to yield. If it doesn't return, Crush abandons it and treats the result as "no opinion" (allowing the agent to proceed).
    • Parallelism: Hooks run in parallel. When rewriting input, the last hook in the config wins. When blocking, the first hook to deny wins.
  6. Separate machine state from user configuration (Planned)

    main

    Concept: Configuration vs. State

    Note: This feature is currently planned and not yet implemented.

    Crush is redesigning how it handles data to distinguish between user-authored configuration and machine-managed state. This prevents mutable machine preferences (like recent models or UI settings) from being treated as static user configuration.

    Proposed Data Roles and Locations

    RoleFormatProposed Path
    User-authored executable configcrushrc / .crushrc~/.config/crush/crushrc or ./crushrc
    Legacy user-authored static configcrush.json / .crush.json~/.config/crush/crush.json or ./crush.json
    Crush-owned persistent stateversioned state.json~/.local/share/crush/state.json or ./state.json
    Session-only changesmemoryN/A
    Credentials/OAuth tokenssecure storageN/A

    Load Precedence

    When determining the effective configuration, Crush will follow this order (from lowest to highest priority):

    1. Built-in defaults
    2. Global state defaults
    3. Workspace state defaults
    4. Global legacy crush.json
    5. Global crushrc
    6. Project legacy crush.json
    7. Project crushrc
    8. Project .crush.json
    9. Project .crushrc
    10. Runtime-only overrides
  7. How the PreToolUse hook runtime works

    main

    The PreToolUse hook runs before a tool is executed.

    Execution Flow

    1. When a tool is called, all PreToolUse hooks with a matching matcher (or no matcher) run in parallel.
    2. Duplicate commands are deduplicated.
    3. The hook receives a JSON payload on stdin and specific environment variables.

    Hook Input (stdin)

    {
      "event": "PreToolUse",
      "session_id": "abc-123",
      "cwd": "/path/to/project",
      "tool_name": "bash",
      "tool_input": { "command": "ls -la" }
    }

    Hook Environment Variables

    • CRUSH_EVENT: Event name (e.g., PreToolUse).
    • CRUSH_TOOL_NAME: Name of the tool being called.
    • CRUSH_SESSION_ID: Current session ID.
    • CRUSH_CWD: Current working directory.
    • CRUSH_PROJECT_DIR: Project root directory.
    • CRUSH_TOOL_INPUT_COMMAND: Value of command from tool input.
    • CRUSH_TOOL_INPUT_FILE_PATH: Value of file_path from tool input.

    Hook Output and Decisions

    Hooks must return an exit code:

    • Exit code 0: Success. Stdout is parsed as JSON to determine the decision.
    • Exit code 2: The tool call is blocked. Stderr is used as the reason.
    • Other exit codes: Non-blocking error; the tool call proceeds.

    JSON Output Format (Stdout):

    {
      "decision": "allow|deny|none",
      "reason": "explanation for deny",
      "context": "optional context appended to tool result",
      "updated_input": "replacement JSON for tool input"
    }

    Decision Aggregation Rules:

    • Deny wins over allow: Any deny decision blocks the call.
    • Allow wins over none: A lone allow lets the call proceed.
    • Input updates: For updated_input, the last non-empty value wins.
    {
      "decision": "allow",
      "context": "optional context appended to tool result"
    }
  8. When to use the question tool

    main

    Use the question tool when:

    • You need to confirm destructive or ambiguous actions.
    • A user's request has multiple valid interpretations.
    • You need the user to pick from a set of options.
    • You need to gather multiple related answers at once.

    Do NOT use the question tool when:

    • The answer can be found by reading code or documentation.
    • The information is obtainable via other tools.
    • You are asking for permission (use the dedicated permission system instead).
  9. Manage file ignoring with .crushignore

    main
    Crush respects .gitignore by default. To exclude specific files or directories from being considered as context without adding them to version control, create a .crushignore file. It uses the same syntax as .gitignore and can be placed in the project root or subdirectories.
  10. Planned: Use `context_files` to inject reference material

    main

    The context_files feature is a planned alternative to the context field for injecting reference material into an agent's context. Instead of inlining full file contents (which consumes tokens and can exceed context windows), context_files allows a hook to return a list of file paths. Crush then informs the agent that these files are relevant, and the agent can choose to open them using its view tool.

    Behavior

    • Path Resolution: Paths are resolved relative to CRUSH_CWD.
    • Missing Files: Non-existent paths are silently dropped with a debug log; hooks do not fail if a file is missing.
    • Aggregation: Paths are concatenated across matching hooks in configuration order and deduplicated.
    • Decision Impact: If the final decision is deny or halt, context_files are dropped.

    Proposed JSON Envelope

    {
      "decision": "allow",
      "context": "Scrubbed one secret",
      "context_files": ["README.md", "docs/ARCHITECTURE.md"]
    }
  11. How multiple hooks compose results

    main

    When multiple hooks match a single tool call, they run in parallel, but their results are composed deterministically based on their order in the crush.json configuration.

    Composition Rules:

    • Deny: If any hook denies, the tool is blocked. reason values are concatenated (newline-separated) in config order.
    • Halt: If any hook halts, the turn ends after the tool is blocked.
    • Allow: If no hook denies/halts but at least one hook returns decision: "allow", the tool proceeds and the permission prompt is skipped.
    • Context: context values are concatenated in config order. Strings and arrays are flattened into a single list of entries.
    • Input Updates: updated_input patches are applied via shallow-merge in config order. Later hooks in the config override earlier ones on colliding keys. (Note: patches are ignored if the final decision is deny or halt).
  12. How Crush hooks work and execute

    main

    When a hook fires, Crush follows a deterministic execution lifecycle:

    1. Filtering: It selects hooks where the matcher regex matches the tool name (if no matcher is provided, it matches all).
    2. Deduplication: Identical commands are run only once.
    3. Parallel Execution: All matching hooks run in parallel via Crush's embedded POSIX shell.
    4. Aggregation: Results are aggregated based on the order they appear in your configuration.
      • Deny wins over allow.
      • Allow wins over no decision.
      • updated_input patches are applied via shallow-merge in config order.
    5. Application: The result is applied before permission checks.
      • A deny decision blocks the tool and skips the permission prompt.
      • An allow decision acts as pre-approval and skips the prompt.
      • Silence (no decision) falls through to the standard permission flow.

    Timeouts: Hooks have a default timeout of 30 seconds. If a hook exceeds this, Crush treats it as a non-blocking error and the tool call proceeds. To ensure long-running hooks are handled correctly, they should honor context cancellation or run as out-of-process shebang-dispatched subprocesses.