# https://github.com/charmbracelet/crush Project Manual

Generated at: 2026-05-31 04:21:28 UTC

## Table of Contents

- [Getting Started with Crush](#getting-started)
- [Project Structure](#project-structure)
- [Configuration Guide](#configuration-guide)
- [LLM Provider Setup](#providers-setup)
- [Built-in Tools](#builtin-tools)
- [Permission System](#permission-system)
- [MCP Integration](#mcp-integration)
- [Skills System](#skills-system)
- [Common Issues and Troubleshooting](#common-issues)
- [Sessions and History](#sessions-and-history)

<a id='getting-started'></a>

## Getting Started with Crush

### Related Pages

Related topics: [Configuration Guide](#configuration-guide), [LLM Provider Setup](#providers-setup)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)
- [internal/app/app.go](https://github.com/charmbracelet/crush/blob/main/internal/app/app.go)
- [internal/app/lsp_events.go](https://github.com/charmbracelet/crush/blob/main/internal/app/lsp_events.go)
- [internal/server/server.go](https://github.com/charmbracelet/crush/blob/main/internal/server/server.go)
- [internal/backend/config.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/config.go)
- [internal/backend/events.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/events.go)
- [internal/client/client.go](https://github.com/charmbracelet/crush/blob/main/internal/client/client.go)
- [internal/commands/commands.go](https://github.com/charmbracelet/crush/blob/main/internal/commands/commands.go)
- [crush/crush.json](https://github.com/charmbracelet/crush/blob/main/crush/crush.json)
</details>

# Getting Started with Crush

Crush is your AI-powered coding assistant that runs directly in your terminal. It connects to your tools, code, and workflows, integrating seamlessly with your preferred LLM provider to assist with development tasks ranging from code editing to complex multi-step operations.

## What is Crush?

Crush is a terminal-based AI coding agent built by [Charm](https://charm.land) that provides:

- **Multi-Model Support**: Connect to a wide range of LLMs including Claude, DeepSeek, OpenAI, Gemini, and custom OpenAI- or Anthropic-compatible APIs
- **Integrated Tool Access**: Execute bash commands, edit files, search codebases, and manage git operations
- **LSP Integration**: Leverage Language Server Protocols for enhanced context awareness
- **MCP Support**: Connect to Model Context Protocol servers for extended capabilities
- **Agent Skills**: Extend functionality through reusable skill packages following the Agent Skills open standard

The architecture follows a client-server model where the server manages workspaces, sessions, and state while clients can connect remotely. Source: [internal/app/app.go:19-50]()

## Installation

Crush supports multiple platforms and installation methods.

### macOS

```bash
# Homebrew
brew install charmbracelet/tap/crush

# MacPorts
sudo port install crush
```

### Linux

```bash
# Binary installer
curl -fsSL https://charm.sh/crush/install.sh | sh

# via nix
nix run github:charmbracelet/crush

# FreeBSD
pkg install crush
```

### Windows

```bash
# Winget
winget install charmbracelet.crush

# Scoop
scoop bucket add charm https://github.com/charmbracelet/scoop-bucket.git
scoop install crush
```

### Nix/NixOS

Crush is available via the official [NUR](https://github.com/nix-community/NUR) in `nur.repos.charmbracelet.crush`. NixOS and Home Manager modules are also available:

```nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    nur.url = "github:nix-community/NUR";
  };
}
```

Source: [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)

## First Launch

Upon first launch, Crush creates its configuration and data directories:

| Platform | Config Location | Data Location |
|----------|----------------|---------------|
| Unix | `$HOME/.config/crush/crush.json` | `$HOME/.local/share/crush/crush.json` |
| Windows | `%LOCALAPPDATA%\crush\crush.json` | `%LOCALAPPDATA%\crush\crush.json` |

You can override these locations using environment variables:

- `CRUSH_GLOBAL_CONFIG` - Override config directory
- `CRUSH_GLOBAL_DATA` - Override data directory

Source: [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)

## Configuration

Crush runs great with no configuration. When needed, configuration is stored as JSON in your project or home directory with the following priority:

1. `.crush.json` (project-local)
2. `crush.json` (project root)
3. `$HOME/.config/crush/crush.json` (global)

### Basic Configuration Structure

```json
{
  "$schema": "https://charm.land/crush.json",
  "model": {
    "large": "claude-sonnet-4-20250514",
    "small": "claude-haiku-4-20250514"
  },
  "providers": {
    "deepseek": {
      "type": "openai-compat",
      "base_url": "https://api.deepseek.com/v1",
      "api_key": "$DEEPSEEK_API_KEY"
    }
  }
}
```

### Configuration Scopes

The backend supports configuration at different scopes. Use the `SetConfigField` method to modify settings:

```go
func (b *Backend) SetConfigField(workspaceID string, scope config.Scope, key string, value any) error
```

Configuration changes are published to all subscribers via the event system, ensuring remote clients refresh their cached config snapshot. Source: [internal/backend/config.go:25-40]()

## Language Server Protocol (LSP) Integration

Crush can use LSPs to provide additional context for informed decision-making. LSPs are configured in your `crush.json`:

```json
{
  "$schema": "https://charm.land/crush.json",
  "lsp": {
    "go": {
      "command": "gopls",
      "env": {
        "GOTOOLCHAIN": "go1.24.5"
      }
    },
    "typescript": {
      "command": "typescript-language-server",
      "args": ["--stdio"]
    },
    "nix": {
      "command": "nil"
    }
  }
}
```

### LSP Event System

The LSP manager tracks server states and emits events:

| Event Type | Description |
|------------|-------------|
| `state_changed` | LSP server state changed (connecting, connected, disconnected) |
| `diagnostics_changed` | Diagnostics for a language were updated |

Source: [internal/app/lsp_events.go:10-45]()

### Accessing LSP Data

```go
// Get all LSP states
states := app.GetLSPStates()

// Get diagnostics for a specific LSP
diagnostics, err := backend.GetLSPDiagnostics(workspaceID, lspName)
```

## Model Context Protocol (MCP) Servers

Crush supports MCP servers through three transport types:

| Transport | Use Case |
|-----------|----------|
| `stdio` | Command-line MCP servers |
| `http` | HTTP endpoints |
| `sse` | Server-Sent Events endpoints |

### Configuration Example

```json
{
  "mcp": {
    "servers": {
      "my-server": {
        "transport": "stdio",
        "command": "npx",
        "args": ["-y", "@some/mcp-server"]
      }
    }
  }
}
```

Shell-style value expansion works in `command`, `args`, `env`, `headers`, and `url`:

- `$VAR` - Environment variable
- `${VAR:-default}` - With fallback
- `$(cat /path/to/secret)` - Command substitution

Source: [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)

## Agent Skills

Crush supports the [Agent Skills](https://agentskills.io) open standard for extending capabilities. Skills are folders containing a `SKILL.md` file.

### Skill Search Paths

Global skill paths (checked in order):

| Priority | Unix Path | Windows Path |
|----------|-----------|-------------|
| 1 | `$CRUSH_SKILLS_DIR` | `$CRUSH_SKILLS_DIR` |
| 2 | `$XDG_CONFIG_HOME/agents/skills` | `%LOCALAPPDATA%\agents\skills\` |
| 3 | `$XDG_CONFIG_HOME/crush/skills` | `%LOCALAPPDATA%\crush\skills\` |
| 4 | `~/.agents/skills/` | `%USERPROFILE%\AppData\Local\agents\skills\` |
| 5 | `~/.claude/skills/` | `%USERPROFILE%\AppData\Local\crush\skills\` |

Project-local paths (relative to project root):

- `.agents/skills`
- `.crush/skills`
- `.claude/skills`
- `.cursor/skills`

### Adding Custom Skill Paths

```json
{
  "$schema": "https://charm.land/crush.json",
  "options": {
    "skills_paths": [
      "~/.config/crush/skills",
      "./project-skills"
    ]
  }
}
```

Source: [README.md](https://github.com/charmbracelet/crush/blob/main/README.md) and [internal/commands/commands.go:25-50]()

## Custom Providers

Crush supports custom provider configurations for both OpenAI-compatible and Anthropic-compatible APIs.

### OpenAI-Compatible APIs

Use `openai-compat` type for non-OpenAI providers with OpenAI-compatible APIs:

```json
{
  "$schema": "https://charm.land/crush.json",
  "providers": {
    "deepseek": {
      "type": "openai-compat",
      "base_url": "https://api.deepseek.com/v1",
      "api_key": "$DEEPSEEK_API_KEY"
    },
    "openrouter": {
      "type": "openai-compat",
      "base_url": "https://openrouter.ai/api/v1",
      "api_key": "$OPENROUTER_API_KEY"
    }
  }
}
```

### Provider Type Selection

> [!NOTE]
> Choose the correct provider type:
> - `openai` - For proxying/routing through OpenAI
> - `openai-compat` - For non-OpenAI providers with OpenAI-compatible APIs

### Model Specification

Models can be specified in multiple formats:

- `"claude-sonnet-4-20250514"` - Direct model ID
- `"deepseek-chat"` - Provider/model format
- `"synthetic/moonshot/kimi-k2"` - Nested provider path

Source: [internal/app/provider.go:10-40]()

## Server-Client Architecture

Crush uses an experimental client-server architecture that can be enabled with:

```bash
CRUSH_CLIENT_SERVER=1 crush
```

### Connection Methods

| Protocol | Unix | Windows |
|----------|------|---------|
| Unix Socket | `unix:///path/to/crush.sock` | - |
| Named Pipe | - | `npipe:////./pipe/crush` |
| TCP | `tcp://host:port` | `tcp://host:port` |

The default host creates a user-specific socket:

```go
func DefaultHost() string {
    sock := "crush.sock"
    usr, _ := user.Current()
    if usr.Uid != "" {
        sock = fmt.Sprintf("crush-%s.sock", usr.Uid)
    }
    if runtime.GOOS == "windows" {
        return fmt.Sprintf("npipe:////./pipe/%s", sock)
    }
    return fmt.Sprintf("unix:///%s/%s", os.UserHomeDir(), sock)
}
```

Source: [internal/server/server.go:35-55]()

### Remote Client Usage

```go
// Connect to default server
client, err := client.DefaultClient("/path/to/workspace")

// List workspaces
workspaces, err := client.ListWorkspaces(ctx)

// Subscribe to events
events, err := backend.SubscribeEvents(ctx, workspaceID)
```

## Common Workflows

### Workflow: Setting Up DeepSeek

```bash
# 1. Install Crush
curl -fsSL https://charm.sh/crush/install.sh | sh

# 2. Set API key
export DEEPSEEK_API_KEY="your-api-key"

# 3. Create config
cat > ~/.config/crush/crush.json << 'EOF'
{
  "$schema": "https://charm.land/crush.json",
  "model": {
    "large": "deepseek-chat",
    "small": "deepseek-coder"
  },
  "providers": {
    "deepseek": {
      "type": "openai-compat",
      "base_url": "https://api.deepseek.com/v1",
      "api_key": "$DEEPSEEK_API_KEY"
    }
  }
}
EOF

# 4. Launch
crush
```

### Workflow: Adding LSP for Go Development

```json
{
  "lsp": {
    "go": {
      "command": "gopls",
      "env": {
        "GOTOOLCHAIN": "auto"
      }
    }
  }
}
```

### Workflow: Using MCP Servers

```json
{
  "mcp": {
    "servers": {
      "filesystem": {
        "transport": "stdio",
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]
      }
    }
  }
}
```

## Architecture Overview

```mermaid
graph TD
    A[User Terminal] --> B[Crush Client]
    B --> C[Crush Server]
    C --> D[Workspace Manager]
    D --> E[App Instance]
    E --> F[Agent Coordinator]
    F --> G[Tool Executor]
    G --> H[LSP Manager]
    G --> I[MCP Client]
    G --> J[Skills Manager]
    C --> K[HTTP/SSE]
    B --> K
```

### Core Components

| Component | Purpose | Location |
|-----------|---------|----------|
| `App` | Main application state and coordination | `internal/app/app.go` |
| `Backend` | Workspace and session management | `internal/backend/` |
| `Server` | HTTP/Unix socket server | `internal/server/` |
| `Client` | Remote client connection | `internal/client/` |
| `LSP Manager` | Language server protocol integration | `internal/lsp/` |
| `Skills Manager` | Agent skills discovery and loading | `internal/skills/` |

Source: [internal/app/app.go:19-50]()

## Troubleshooting

### Database Corruption

If you experience database corruption errors, check:

1. Filesystem integrity with `fsck`
2. Disk health with SMART tools
3. Ensure you're not running multiple instances simultaneously that share the same data directory

### LSP Configuration Not Working

For global LSP config issues:

1. Verify the config path: `~/.config/crush/crush.json`
2. Check the LSP command is in your PATH
3. Test the LSP directly: `gopls version` (for Go)

### OpenRouter/API Authentication Errors

```bash
# Verify your API key is set
echo $OPENROUTER_API_KEY

# Test the key directly
curl -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  https://openrouter.ai/api/v1/models
```

### Bash Tool Hanging

Recent versions fixed issues with interactive commands like `git rebase -i`. If you experience hangs:

1. Update to the latest version
2. Avoid running truly interactive commands through the bash tool
3. Use dedicated tools (git tool) for git operations

Source: [Community Issues](https://github.com/charmbracelet/crush/issues)

## Next Steps

- **Configure your preferred model** in `crush.json`
- **Add LSP support** for your primary language
- **Explore MCP servers** for extended capabilities
- **Create custom skills** by adding `SKILL.md` files to your skills directories
- **Enable client-server mode** with `CRUSH_CLIENT_SERVER=1` for remote access

For more advanced configuration, ask Crush to configure itself using the built-in `crush-config` skill.

---

<a id='project-structure'></a>

## Project Structure

### Related Pages

Related topics: [Getting Started with Crush](#getting-started)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [internal/app/testing.go](https://github.com/charmbracelet/crush/blob/main/internal/app/testing.go)
- [internal/backend/backend_test.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/backend_test.go)
- [internal/server/config.go](https://github.com/charmbracelet/crush/blob/main/internal/server/config.go)
- [internal/server/events.go](https://github.com/charmbracelet/crush/blob/main/internal/server/events.go)
- [internal/server/proto.go](https://github.com/charmbracelet/crush/blob/main/internal/server/proto.go)
- [internal/server/server.go](https://github.com/charmbracelet/crush/blob/main/internal/server/server.go)
- [internal/client/config.go](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go)
- [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)
</details>

# Project Structure

## Overview

Crush is a terminal-based AI coding assistant built by Charm that integrates tools, code, and workflows with LLM models. The project follows a **client-server architecture** where the core logic runs as a backend server, with clients (including CLI and potentially IDE integrations) communicating via HTTP APIs.

The architecture enables:
- **Multi-workspace support** - Multiple project directories can be managed simultaneously
- **Session persistence** - Conversations and context are stored in a database
- **Extensible tooling** - LSPs (Language Server Protocols) and MCPs (Model Context Protocols) enhance agent capabilities
- **Multi-provider model support** - Various LLM providers can be configured and switched at runtime

## High-Level Architecture

```mermaid
graph TD
    subgraph "Client Layer"
        CLI[CLI / TUI Client]
        IDE[IDE Integration]
    end
    
    subgraph "Server Layer"
        Server[HTTP Server]
        Controller[API Controller]
    end
    
    subgraph "Backend Core"
        Backend[Backend Manager]
        Workspace[Workspace Manager]
        Session[Session Manager]
        Permission[Permission System]
    end
    
    subgraph "Data Layer"
        DB[(Database)]
        Config[Config Store]
    end
    
    subgraph "External Integrations"
        LSP[LSP Servers]
        MCP[MCP Servers]
        LLM[LLM Providers]
    end
    
    CLI --> Server
    IDE --> Server
    Server --> Controller
    Controller --> Backend
    Backend --> Workspace
    Backend --> Session
    Backend --> Permission
    Backend --> DB
    Backend --> Config
    Backend --> LSP
    Backend --> MCP
    Backend --> LLM
```

## Directory Structure

### Core Application

| Path | Purpose |
|------|---------|
| `main.go` | Application entry point |
| `internal/app/` | Application lifecycle and shutdown management |
| `internal/backend/` | Core backend logic and workspace management |
| `internal/db/` | Database operations and persistence |
| `internal/config/` | Configuration management and stores |

### Server Layer

| Path | Purpose |
|------|---------|
| `internal/server/` | HTTP server implementation |
| `internal/server/server.go` | Server initialization and routing |
| `internal/server/config.go` | Configuration API handlers |
| `internal/server/events.go` | Event conversion and SSE (Server-Sent Events) |
| `internal/server/proto.go` | Protocol definitions and API contracts |

### Client Layer

| Path | Purpose |
|------|---------|
| `internal/client/` | Client library for server communication |
| `internal/client/config.go` | Client-side configuration operations |

## Server Architecture

The server layer implements a RESTful API with support for Server-Sent Events (SSE) for real-time updates.

### Route Registration

Routes are registered in `internal/server/server.go` using Go's `http.ServeMux`:

```go
mux.HandleFunc("GET /v1/health", c.handleGetHealth)
mux.HandleFunc("GET /v1/version", c.handleGetVersion)
mux.HandleFunc("GET /v1/config", c.handleGetConfig)
mux.HandleFunc("POST /v1/control", c.handlePostControl)
mux.HandleFunc("GET /v1/workspaces", c.handleGetWorkspaces)
```

Source: [internal/server/server.go:1-50]()

### API Endpoints

The server exposes the following endpoint categories:

| Category | Endpoints | Description |
|----------|-----------|-------------|
| **System** | `GET /v1/health`, `GET /v1/version`, `POST /v1/control` | Server health, version, and control commands |
| **Configuration** | `GET /v1/config`, workspace config endpoints | Global and workspace-specific configuration |
| **Workspaces** | `GET /v1/workspaces`, workspace CRUD | Workspace management |
| **Project** | Project initialization, prompts | Project setup and context |

### Event System

Events flow from the backend to clients via SSE envelopes:

```go
type envelope struct {
    Type    string      `json:"type"`
    Payload interface{} `json:"payload"`
}
```

Source: [internal/server/events.go:1-30]()

Supported event types include:
- **LSP Events** - Language server diagnostics and state changes
- **MCP Events** - Model Context Protocol server status
- **Permission Events** - Tool execution authorization requests and notifications
- **Skills Events** - Agent skill discovery and loading states

## Backend Architecture

The backend (`internal/backend/`) is the core of Crush's functionality.

### Workspace Management

Workspaces are identified by unique UUIDs and resolved paths:

```go
type Workspace struct {
    ID           string
    Path         string
    resolvedPath string
    clients      map[string]*clientState
    shutdownFn   func()
}
```

Source: [internal/backend/backend_test.go:1-20]()

Key workspace operations:
- **Path Resolution** - Handles absolute paths and symlinks
- **Client Tracking** - Manages multiple client connections per workspace
- **Graceful Shutdown** - Coordinates cleanup when workspaces are removed

### Client State Management

Each connected client has an associated state:

```go
type clientState struct {
    ID          string
    WorkspaceID string
    LastSeen    time.Time
    // ...
}
```

### Backend Lifecycle

Backend shutdown follows a graceful pattern:

```go
s.backend.Shutdown()
```

The server shutdown callback triggers a coordinated shutdown sequence:

```go
s.backend = backend.New(context.Background(), cfg, func() {
    go func() {
        slog.Info("Shutting down server...")
        if err := s.Shutdown(context.Background()); err != nil {
            slog.Error("Failed to shutdown server", "error", err)
        }
    }()
})
```

Source: [internal/server/server.go:1-50]()

## Application Layer

The application layer (`internal/app/`) manages the main application lifecycle.

### Shutdown Management

The app maintains a list of cleanup functions:

```go
func (app *App) ShutdownForTest() {
    for _, cleanup := range app.cleanupFuncs {
        if cleanup != nil {
            _ = cleanup(context.Background())
        }
    }
    app.cleanupFuncs = nil
}
```

Source: [internal/app/testing.go:1-15]()

This pattern drives a full production shutdown path including:
- Database release
- LSP teardown
- MCP server shutdown

## Configuration System

### Configuration Precedence

Crush supports layered configuration with the following priority (highest to lowest):

1. `.crush.json` - Project-local configuration
2. `crush.json` - Repository-local configuration  
3. `$HOME/.config/crush/crush.json` - Global user configuration

### Configuration Locations

| Type | Unix Path | Windows Path |
|------|-----------|--------------|
| **Config** | `$HOME/.config/crush/crush.json` | `%LOCALAPPDATA%\crush\crush.json` |
| **Data** | `$HOME/.local/share/crush/crush.json` | `%LOCALAPPDATA%\crush\crush.json` |

### Environment Variable Overrides

| Variable | Purpose |
|----------|---------|
| `CRUSH_GLOBAL_CONFIG` | Override config file location |
| `CRUSH_GLOBAL_DATA` | Override data directory location |
| `CRUSH_CLIENT_SERVER` | Enable experimental client-server mode |

### Configuration Schema

```json
{
  "$schema": "https://charm.land/crush.json",
  "lsp": { },
  "mcp": { },
  "providers": { },
  "permissions": { },
  "options": { }
}
```

## Client-Server Communication

### Client API Operations

The client library (`internal/client/config.go`) provides methods for:

| Operation | Method | Description |
|-----------|--------|-------------|
| **Set Preferred Model** | `SetPreferredModel()` | Update workspace's default model |
| **Set Compact Mode** | `SetCompactMode()` | Toggle compact mode for responses |
| **Set Provider API Key** | `SetProviderAPIKey()` | Configure provider credentials |
| **Import Copilot** | `ImportCopilot()` | OAuth token import for GitHub Copilot |
| **Refresh OAuth Token** | `RefreshOAuthToken()` | Token refresh for OAuth providers |
| **MCP Resources** | `GetMCPResource()` | Read MCP server resources |
| **MCP Prompts** | `GetMCPPrompt()` | Retrieve prompts from MCP servers |

### API Key Handling

API credentials are transmitted with explicit type information:

```go
switch v := apiKey.(type) {
case string:
    kind = proto.APIKeyKindString
    raw, _ = json.Marshal(v)
case []byte:
    kind = proto.APIKeyKindBytes
    raw = v
}
```

Source: [internal/client/config.go:1-50]()

This ensures type preservation across the JSON wire format.

## Workspace Initialization

Workspaces can be marked as initialized via the API:

```go
// @Summary Mark project as initialized
// @Router /workspaces/{id}/project/init [post]
func (c *controllerV1) handlePostWorkspaceProjectInit(w http.ResponseWriter, r *http.Request)
```

The backend stores initialization state and can provide initialization prompts:

```go
// @Router /workspaces/{id}/project/init-prompt [get]
func (c *controllerV1) handleGetWorkspaceProjectInitPrompt(w http.ResponseWriter, r *http.Request)
```

Source: [internal/server/config.go:1-50]()

## Event Type Conversion

The events system converts internal event types to wire format for SSE delivery:

```go
func messageToProto(m message.Message) proto.Message {
    msg := proto.Message{
        Role:    proto.MessageRole(m.Role),
        Parts:   make([]any, 0, len(m.Parts)),
    }
    for _, part := range m.Parts {
        switch v := part.(type) {
        case message.TextContent:
            msg.Parts = append(msg.Parts, proto.TextContent{Text: v.Text})
        case message.ToolCall:
            msg.Parts = append(msg.Parts, proto.ToolCall{...})
        }
    }
    return msg
}
```

Source: [internal/server/events.go:1-100]()

## Testing Infrastructure

### Backend Test Helpers

The backend provides test utilities for creating isolated workspaces:

```go
func addTestWorkspace(t *testing.T, b *Backend, key string) (*Workspace, *atomic.Int32) {
    var shutdowns atomic.Int32
    ws := &Workspace{
        ID:           uuid.New().String(),
        Path:         key,
        resolvedPath: key,
        clients:      make(map[string]*clientState),
        shutdownFn:   func() { shutdowns.Add(1) },
    }
    b.mu.Lock()
    b.workspaces.Set(ws.ID, ws)
    b.pathIndex[key] = ws.ID
    b.mu.Unlock()
    return ws, &shutdowns
}
```

Source: [internal/backend/backend_test.go:1-30]()

### Event Round-Trip Testing

Events are validated for JSON marshaling round-trips:

```go
func TestSkillsEventToProto_RoundTrip(t *testing.T) {
    env := wrapEvent(src)
    // Validate serialization preserves state values
}
```

Source: [internal/server/events_test.go:1-50]()

## External Integrations

### Language Server Protocol (LSP)

Configured LSPs provide context for code understanding:

```json
{
  "lsp": {
    "go": {
      "command": "gopls",
      "env": { "GOTOOLCHAIN": "go1.24.5" }
    }
  }
}
```

### Model Context Protocol (MCP)

MCP servers extend agent capabilities through stdio, HTTP, or SSE transports:

```json
{
  "mcp": {
    "servers": [
      {
        "name": "filesystem",
        "transport": "stdio",
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
      }
    ]
  }
}
```

## Known Issues and Community Feedback

The following community-reported issues relate to the project structure:

| Issue | Description |
|-------|-------------|
| [#2935](https://github.com/charmbracelet/crush/issues/2935) | Feature request for multiple permission profiles (Yolo/Safe/Custom) switchable at runtime |
| [#928](https://github.com/charmbracelet/crush/issues/928) | Requests to remove bash command security limits for unrestricted terminal access |
| [#2898](https://github.com/charmbracelet/crush/issues/2898) | Global LSP config not working - relates to configuration precedence |
| [#2903](https://github.com/charmbracelet/crush/issues/2903) | Database corruption issues in certain environments |
| [#2928](https://github.com/charmbracelet/crush/issues/2928) | OpenRouter model authentication failures |

## Summary

Crush's architecture separates concerns into:

1. **Client Layer** - CLI/TUI interfaces that communicate with the server
2. **Server Layer** - HTTP API handling, routing, and event distribution
3. **Backend Core** - Workspace management, session handling, and tool orchestration
4. **Data Layer** - Database persistence and configuration management
5. **Integration Layer** - LSP and MCP server connections

This modular structure enables:
- **Scalability** - Multiple clients can connect to a single server instance
- **Extensibility** - New providers and protocols can be added without core changes
- **Testability** - Each layer can be tested in isolation
- **Persistence** - Sessions survive client restarts

---

<a id='configuration-guide'></a>

## Configuration Guide

### Related Pages

Related topics: [Getting Started with Crush](#getting-started), [LLM Provider Setup](#providers-setup), [Permission System](#permission-system)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)
- [internal/config/config.go](https://github.com/charmbracelet/crush/blob/main/internal/config/config.go)
- [internal/config/load.go](https://github.com/charmbracelet/crush/blob/main/internal/config/load.go)
- [internal/backend/backend.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/backend.go)
- [internal/server/server.go](https://github.com/charmbracelet/crush/blob/main/internal/server/server.go)
- [internal/server/config.go](https://github.com/charmbracelet/crush/blob/main/internal/server/config.go)
- [internal/client/config.go](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go)
- [docs/hooks/README.md](https://github.com/charmbracelet/crush/blob/main/docs/hooks/README.md)
</details>

# Configuration Guide

Crush is designed to work out-of-the-box with sensible defaults, but provides extensive configuration capabilities for customizing behavior, integrating with external tools, and adapting to specific workflows.

## Configuration File Locations and Priority

Crush loads configuration from multiple locations with the following priority order (highest to lowest):

1. `.crush.json` (project-local)
2. `crush.json` (project root)
3. `$HOME/.config/crush/crush.json` (user global)

Source: [README.md:1](https://github.com/charmbracelet/crush/blob/main/README.md)

### Environment Variable Overrides

You can override the default configuration and data directory locations using environment variables:

| Environment Variable | Purpose |
|---------------------|---------|
| `CRUSH_GLOBAL_CONFIG` | Override the global configuration file location |
| `CRUSH_GLOBAL_DATA` | Override the user data directory location |
| `CRUSH_SKILLS_DIR` | Override the skills directory location |

Source: [README.md:1](https://github.com/charmbracelet/crush/blob/main/README.md)

### Ephemeral State Storage

In addition to configuration, Crush stores ephemeral application state (such as session data) in:

- **Unix**: `$HOME/.local/share/crush/crush.json`
- **Windows**: `%LOCALAPPDATA%\crush\crush.json`

Source: [README.md:1](https://github.com/charmbracelet/crush/blob/main/README.md)

## Configuration File Structure

The configuration file is a JSON object with the following schema:

```json
{
  "$schema": "https://charm.land/crush.json",
  "lsp": { ... },
  "mcp": { ... },
  "providers": { ... },
  "permissions": { ... },
  "hooks": { ... },
  "options": { ... },
  "commit": { ... }
}
```

Source: [README.md:1](https://github.com/charmbracelet/crush/blob/main/README.md)

## LSP Configuration

Crush integrates with Language Server Protocol (LSP) servers to provide additional context for code-aware decisions. LSPs are configured under the `lsp` key.

### Basic LSP Configuration

```json
{
  "$schema": "https://charm.land/crush.json",
  "lsp": {
    "go": {
      "command": "gopls",
      "env": {
        "GOTOOLCHAIN": "go1.24.5"
      }
    },
    "typescript": {
      "command": "typescript-language-server",
      "args": ["--stdio"]
    },
    "nix": {
      "command": "nil"
    }
  }
}
```

Source: [README.md:1](https://github.com/charmbracelet/crush/blob/main/README.md)

### LSP Configuration Options

| Field | Type | Description |
|-------|------|-------------|
| `command` | string | The LSP server executable |
| `args` | string[] | Command-line arguments |
| `env` | object | Environment variables for the LSP process |
| `root_markers` | string[] | Files/directories that identify the project root |
| `filetypes` | string[] | File extensions the LSP should handle |

### Backend LSP Management

The backend provides programmatic LSP management through the `LSPManager`:

```go
// LSPStart starts an LSP server for the given path.
func (b *Backend) LSPStart(ctx context.Context, workspaceID, path string) error

// LSPStopAll stops all LSP servers for a workspace.
func (b *Backend) LSPStopAll(ctx context.Context, workspaceID string) error

// GetLSPDiagnostics retrieves diagnostics from an LSP client.
func (b *Backend) GetLSPDiagnostics(workspaceID, lspName string) (any, error)
```

Source: [internal/backend/backend.go:1](https://github.com/charmbracelet/crush/blob/main/internal/backend/backend.go)

## MCP Configuration

Crush supports Model Context Protocol (MCP) servers for extending capabilities with external tools and resources.

### Transport Types

MCP servers can be configured using three transport types:

| Transport | Use Case |
|-----------|----------|
| `stdio` | Command-line servers that communicate via stdin/stdout |
| `http` | HTTP endpoints for web-based MCP servers |
| `sse` | Server-Sent Events for streaming responses |

### MCP Configuration Example

```json
{
  "mcp": {
    "servers": {
      "my-server": {
        "transport": "stdio",
        "command": "npx",
        "args": ["-y", "@some/mcp-server"]
      }
    }
  }
}
```

### Shell Expansion in MCP Config

Shell-style value expansion works in `command`, `args`, `env`, `headers`, and `url` fields:

- `$VAR` - Simple variable expansion
- `${VAR:-default}` - Default value if variable is unset
- `$(command)` - Command substitution
- Quoting and nesting are supported

This enables file-based secrets integration:

```json
{
  "command": "$MCP_SERVER",
  "url": "$(cat /path/to/secret/token)"
}
```

Source: [README.md:1](https://github.com/charmbracelet/crush/blob/main/README.md)

### MCP Client API

The client provides methods for interacting with MCP servers:

```go
// GetMCPServers retrieves all configured MCP servers for a workspace.
func (c *Client) GetMCPServers(ctx context.Context, id string) ([]MCPServer, error)

// GetMCPResources retrieves available resources from a server.
func (c *Client) GetMCPResources(ctx context.Context, id, serverID string) ([]MCPResourceContents, error)

// GetMCPPrompt retrieves a prompt from a named MCP server.
func (c *Client) GetMCPPrompt(ctx context.Context, id, clientID, promptID string, args map[string]string) (string, error)
```

Source: [internal/client/config.go:1](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go)

## Provider Configuration

Crush supports multiple LLM providers with various authentication methods.

### Built-in Provider Environment Variables

| Environment Variable | Provider |
|---------------------|----------|
| `ANTHROPIC_API_KEY` | Anthropic |
| `OPENAI_API_KEY` | OpenAI |
| `VERCEL_API_KEY` | Vercel AI Gateway |
| `GEMINI_API_KEY` | Google Gemini |
| `SYNTHETIC_API_KEY` | Synthetic |
| `ZAI_API_KEY` | Z.ai |
| `MINIMAX_API_KEY` | MiniMax |
| `HF_TOKEN` | Hugging Face Inference |
| `CEREBRAS_API_KEY` | Cerebras |
| `OPENROUTER_API_KEY` | OpenRouter |
| `IONET_API_KEY` | io.net |
| `ALIBABA_SINGAPORE_API_KEY` | Alibaba (Singapore) |
| `GROQ_API_KEY` | Groq |
| `AVIAN_API_KEY` | Avian |
| `OPENCODE_API_KEY` | OpenCode Zen & Go |
| `VERTEXAI_PROJECT` | Google Cloud VertexAI |
| `VERTEXAI_LOCATION` | Google Cloud VertexAI |
| `AWS_ACCESS_KEY_ID` | Amazon Bedrock (Claude) |
| `AWS_SECRET_ACCESS_KEY` | Amazon Bedrock (Claude) |
| `AWS_REGION` | Amazon Bedrock (Claude) |
| `AWS_PROFILE` | Amazon Bedrock (Custom Profile) |
| `AWS_BEARER_TOKEN_BEDROCK` | Amazon Bedrock |

Source: [README.md:1](https://github.com/charmbracelet/crush/blob/main/README.md)

### Custom Providers

#### OpenAI-Compatible APIs

For non-OpenAI providers with OpenAI-compatible APIs, use `openai-compat`:

```json
{
  "$schema": "https://charm.land/crush.json",
  "providers": {
    "deepseek": {
      "type": "openai-compat",
      "base_url": "https://api.deepseek.com/v1",
      "api_key": "$DEEPSEEK_API_KEY"
    }
  }
}
```

> [!NOTE]
> Two provider types are supported for OpenAI-style APIs:
> - `openai` - Use when proxying through OpenAI
> - `openai-compat` - Use for non-OpenAI providers with OpenAI-compatible APIs

Source: [README.md:1](https://github.com/charmbracelet/crush/blob/main/README.md)

### Provider API Management

The client supports dynamic provider API key management:

```go
// SetProviderAPIKey sets a provider API key on the server.
// The wire format tags the credential with an explicit Kind so the server
// can decode it back into the right Go type.
func (c *Client) SetProviderAPIKey(ctx context.Context, id string, scope config.Scope, providerID string, apiKey any) error
```

Source: [internal/client/config.go:1](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go)

### OAuth Providers

Crush supports OAuth-based authentication for providers like GitHub Copilot:

```go
// ImportCopilot attempts to import a GitHub Copilot token on the server.
func (c *Client) ImportCopilot(ctx context.Context, id string) (*oauth.Token, bool, error)

// RefreshOAuthToken refreshes an OAuth token for a provider.
func (c *Client) RefreshOAuthToken(ctx context.Context, id string, scope config.Scope, providerID string) error
```

Source: [internal/client/config.go:1](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go)

## Permission Configuration

Crush implements a permission system for controlling tool execution. Permissions can be configured at different scopes.

### Permission Scopes

| Scope | Description |
|-------|-------------|
| `global` | Applied to all workspaces |
| `workspace` | Applied to a specific workspace |
| `session` | Applied to a single session |

Source: [internal/backend/backend.go:1](https://github.com/charmbracelet/crush/blob/main/internal/backend/backend.go)

### Yolo Mode

When `permissions.yolo` is set to `true`, all tools are auto-approved without prompting. This is suitable for trusted environments:

```json
{
  "permissions": {
    "yolo": true
  }
}
```

### Permission Events

The system publishes permission events for auditing:

```go
// PermissionRequest represents a request for tool execution permission
type PermissionRequest struct {
    ID          string
    SessionID   string
    ToolCallID  string
    ToolName    string
    Description string
    Action      string
    Path        string
    Params      map[string]any
}

// PermissionNotification represents a notification about a permission action
type PermissionNotification struct {
    ToolCallID string
    // ... additional fields
}
```

Source: [internal/server/events.go:1](https://github.com/charmbracelet/crush/blob/main/internal/server/events.go)

## Hooks Configuration

Hooks allow you to run custom scripts at specific points in the agent workflow.

### Hook Types

Crush supports hooks for various lifecycle events. Hooks use the built-in shell interpreter by default (since v0.67.0), improving cross-platform support.

### Hooks Configuration

```json
{
  "hooks": {
    "pre_tool_call": "/path/to/pre-hook.sh",
    "post_tool_call": "/path/to/post-hook.sh",
    "on_error": "/path/to/error-handler.sh"
  }
}
```

Source: [docs/hooks/README.md](https://github.com/charmbracelet/crush/blob/main/docs/hooks/README.md)

### Hooks Use Built-in Shell

By default, Hooks will now use the built-in shell interpreter instead of the system shell. This particularly improves support on Windows and other platforms where bash may not be available.

Source: [README.md:1](https://github.com/charmbracelet/crush/blob/main/README.md) (v0.67.0 release notes)

## Commit Configuration

Crush can assist with git commits with configurable attribution styles:

```json
{
  "commit": {
    "trailer_style": "assisted-by",
    "generated_with": true
  }
}
```

### Commit Configuration Options

| Option | Type | Description |
|--------|------|-------------|
| `trailer_style` | string | Attribution trailer format |
| `generated_with` | boolean | Add "💘 Generated with Crush" line |

### Trailer Style Options

| Value | Format |
|-------|--------|
| `assisted-by` | `Assisted-by: Crush:[ModelID]` (kernel convention) |
| `co-authored-by` | `Co-Authored-By: Crush <crush@charm.land>` |
| `none` | No attribution trailer |

Source: [README.md:1](https://github.com/charmbracelet/crush/blob/main/README.md)

## Skills Configuration

Crush supports the Agent Skills open standard for extending capabilities.

### Skills Search Paths

Skills are discovered from these locations (in order):

**Global paths:**
- `$CRUSH_SKILLS_DIR`
- `$XDG_CONFIG_HOME/agents/skills` or `~/.config/agents/skills/`
- `$XDG_CONFIG_HOME/crush/skills` or `~/.config/crush/skills/`
- `~/.agents/skills/`
- `~/.claude/skills/`

**Windows-specific:**
- `%LOCALAPPDATA%\agents\skills\`
- `%LOCALAPPDATA%\crush\skills\`

**Project-local paths:**
- `.agents/skills`
- `.crush/skills`
- `.claude/skills`
- `.cursor/skills`

### Custom Skills Paths

Add custom skills directories:

```json
{
  "$schema": "https://charm.land/crush.json",
  "options": {
    "skills_paths": [
      "~/.config/crush/skills",
      "./project-skills"
    ]
  }
}
```

### Skills Events

Skills discovery and loading are exposed via events:

```go
// SkillsEvent represents skill discovery state changes
type SkillsEvent struct {
    States []SkillState
}

// SkillState represents the state of a discovered skill
type SkillState struct {
    Name  string
    Path  string
    State SkillDiscoveryState
    Err   error
}
```

Source: [internal/server/events.go:1](https://github.com/charmbracelet/crush/blob/main/internal/server/events.go)

## Options Configuration

General Crush behavior can be customized through the `options` key.

### Common Options

| Option | Type | Description |
|--------|------|-------------|
| `disabled_skills` | string[] | List of skills to disable |
| `skills_paths` | string[] | Additional directories to search for skills |
| `compact` | boolean | Enable compact UI mode |

### Example

```json
{
  "$schema": "https://charm.land/crush.json",
  "options": {
    "disabled_skills": ["crush-config"]
  }
}
```

## Compact Mode

Compact mode adjusts the UI to use less screen space, useful for terminal sessions with limited height.

```go
// SetCompactMode sets compact mode on the server.
func (c *Client) SetCompactMode(ctx context.Context, id string, scope config.Scope, enabled bool) error
```

Source: [internal/client/config.go:1](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go)

## Server-Client Architecture

Crush supports an experimental server-client architecture (enabled with `CRUSH_CLIENT_SERVER=1`) where a persistent server handles workspace management and clients connect via HTTP.

### Configuration in Server Mode

When running in server mode, the backend handles workspace-level configuration:

```go
// NewServer creates a new Server with the given network and address.
func NewServer(cfg *config.ConfigStore, network, address string) *Server

// GetWorkspaceConfig returns the workspace-level configuration.
func (b *Backend) GetWorkspaceConfig(workspaceID string) (*config.Config, error)

// GetWorkspaceProviders returns the configured providers for a workspace.
func (b *Backend) GetWorkspaceProviders(workspaceID string) (any, error)
```

Source: [internal/server/server.go:1](https://github.com/charmbracelet/crush/blob/main/internal/server/server.go)
Source: [internal/backend/backend.go:1](https://github.com/charmbracelet/crush/blob/main/internal/backend/backend.go)

### Dynamic Configuration Updates

Configuration can be modified at runtime through the server API:

```go
// handlePostWorkspaceConfigSet sets a configuration field.
// POST /workspaces/{id}/config/set
func (c *controllerV1) handlePostWorkspaceConfigSet(w http.ResponseWriter, r *http.Request)

// handlePostWorkspaceConfigRemove removes a configuration field.
// POST /workspaces/{id}/config/remove
func (c *controllerV1) handlePostWorkspaceConfigRemove(w http.ResponseWriter, r *http.Request)
```

Source: [internal/server/config.go:1](https://github.com/charmbracelet/crush/blob/main/internal/server/config.go)

## Configuration Architecture Diagram

```mermaid
graph TD
    A[Configuration Files] --> B[Load Priority]
    B --> C1[.crush.json]
    B --> C2[crush.json]
    B --> C3[~/.config/crush/crush.json]
    
    C1 --> D[Config Store]
    C2 --> D
    C3 --> D
    
    D --> E[Workspace Config]
    D --> F[Global Config]
    
    E --> G[Backend]
    F --> G
    
    G --> H[LSP Manager]
    G --> I[MCP Manager]
    G --> J[Permission System]
    G --> K[Provider Manager]
    
    L[Environment Variables] --> D
    L --> M[API Keys]
    L --> N[Overrides]
    
    H --> O[Language Servers]
    I --> P[MCP Servers]
    J --> Q[Tool Execution]
    K --> R[LLM Providers]
```

## Common Configuration Issues

### Global LSP Config Not Working

Users have reported issues with global LSP configuration not being applied. Ensure the configuration file is in the correct location and the JSON syntax is valid.

**Known issue:** LSP configuration in `~/.config/crush/crush.json` may not be applied correctly in some versions. Source: [GitHub Issue #2898](https://github.com/charmbracelet/crush/issues/2898)

### OpenRouter API Key Not Working

When using OpenRouter, ensure the API key is properly exported:

```bash
export OPENROUTER_API_KEY=sk-or-v1-...
```

The key should be set in the environment, not in the config file directly.

Source: [GitHub Issue #2928](https://github.com/charmbracelet/crush/issues/2928)

### Database Corruption

The database stores session and workspace metadata. Corruption can occur from concurrent access or abrupt termination. The database is located at the data path defined by `CRUSH_GLOBAL_DATA`.

Source: [GitHub Issue #2903](https://github.com/charmbracelet/crush/issues/2903)

## Built-in Configuration Skill

Crush ships with a builtin `crush-config` skill for configuring itself. You can simply ask Crush to configure settings:

```
Configure OpenRouter API key
```

This skill can be disabled if needed:

```json
{
  "options": {
    "disabled_skills": ["crush-config"]
  }
}
```

Source: [README.md:1](https://github.com/charmbracelet/crush/blob/main/README.md)

## Testing Configuration

For testing purposes, the application provides a `ShutdownForTest` method that runs cleanup functions without triggering full production shutdown:

```go
func (app *App) ShutdownForTest() {
    for _, cleanup := range app.cleanupFuncs {
        if cleanup != nil {
            _ = cleanup(context.Background())
        }
    }
    app.cleanupFuncs = nil
}
```

Source: [internal/app/testing.go:1](https://github.com/charmbracelet/crush/blob/main/internal/app/testing.go)

---

<a id='providers-setup'></a>

## LLM Provider Setup

### Related Pages

Related topics: [Getting Started with Crush](#getting-started), [Configuration Guide](#configuration-guide)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)
- [internal/app/provider.go](https://github.com/charmbracelet/crush/blob/main/internal/app/provider.go)
- [internal/app/provider_test.go](https://github.com/charmbracelet/crush/blob/main/internal/app/provider_test.go)
- [internal/client/config.go](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go)
- [internal/backend/events.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/events.go)
</details>

# LLM Provider Setup

Crush supports a wide variety of LLM providers, allowing users to choose their preferred AI models for coding assistance. This document covers how providers are configured, how to set up API keys, and how to add custom providers.

## Overview

LLM providers in Crush are the bridges between the application and various AI model services. Each provider handles authentication, request formatting, and response parsing for specific API types. The provider system is designed to be extensible, supporting both built-in providers and custom configurations.

Source: [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)

## Supported Built-in Providers

Crush ships with first-class support for numerous LLM providers. The following table summarizes the available providers and their authentication methods:

| Provider | Environment Variable | API Type |
|----------|---------------------|----------|
| Anthropic | `ANTHROPIC_API_KEY` | Anthropic |
| OpenAI | `OPENAI_API_KEY` | OpenAI |
| DeepSeek | `DEEPSEEK_API_KEY` | OpenAI-compatible |
| OpenRouter | `OPENROUTER_API_KEY` | OpenAI-compatible |
| Vercel AI Gateway | `VERCEL_API_KEY` | OpenAI-compatible |
| Google Gemini | `GEMINI_API_KEY` | OpenAI-compatible |
| Hyper | OAuth (Device Flow) | Anthropic-compatible |
| GitHub Copilot | OAuth (Token Import) | OpenAI-compatible |
| Synthetic | `SYNTHETIC_API_KEY` | OpenAI-compatible |
| Z.ai | `ZAI_API_KEY` | OpenAI-compatible |
| MiniMax | `MINIMAX_API_KEY` | OpenAI-compatible |
| Hugging Face | `HF_TOKEN` | OpenAI-compatible |
| Cerebras | `CEREBRAS_API_KEY` | OpenAI-compatible |
| io.net | `IONET_API_KEY` | OpenAI-compatible |
| Alibaba (Singapore) | `ALIBABA_SINGAPORE_API_KEY` | OpenAI-compatible |
| Groq | `GROQ_API_KEY` | OpenAI-compatible |
| Avian | `AVIAN_API_KEY` | OpenAI-compatible |
| OpenCode Zen & Go | `OPENCODE_API_KEY` | OpenAI-compatible |
| Google VertexAI | `VERTEXAI_PROJECT`, `VERTEXAI_LOCATION` | Anthropic-compatible |
| Amazon Bedrock | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION` | Anthropic-compatible |

Source: [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)

## Configuration Structure

Providers are configured in the `crush.json` file under the `providers` key. Configuration can be set at three levels with the following priority:

1. `.crush.json` (project-local)
2. `crush.json` (project root)
3. `$HOME/.config/crush/crush.json` (global)

### Basic Provider Configuration

```json
{
  "providers": {
    "provider-name": {
      "type": "openai-compat",
      "base_url": "https://api.provider.com/v1",
      "api_key": "$PROVIDER_API_KEY",
      "models": {
        "large": ["model-id-1", "model-id-2"],
        "small": ["model-id-3"]
      }
    }
  }
}
```

Source: [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)

## API Key Management

### Setting API Keys via Environment Variables

The recommended approach for API key configuration is using environment variables. This keeps sensitive credentials out of configuration files:

```bash
export ANTHROPIC_API_KEY="sk-ant-..."
export OPENAI_API_KEY="sk-..."
export OPENROUTER_API_KEY="sk-or-..."
```

### Setting API Keys via Configuration

API keys can also be set directly in the configuration file using string references:

```json
{
  "providers": {
    "deepseek": {
      "type": "openai-compat",
      "base_url": "https://api.deepseek.com/v1",
      "api_key": "$DEEPSEEK_API_KEY"
    }
  }
}
```

### Programmatic API Key Setting

The client provides methods to set provider API keys dynamically:

```go
err := client.SetProviderAPIKey(ctx, workspaceID, config.ScopeGlobal, "openai", "api-key-string")
```

Source: [internal/client/config.go:35-55](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go#L35-L55)

## Custom Provider Setup

Crush supports two types of custom providers:

| Type | Use Case |
|------|----------|
| `openai` | When proxying or routing requests through OpenAI |
| `openai-compat` | When using non-OpenAI providers with OpenAI-compatible APIs |

### OpenAI-Compatible Provider Example

Here's an example configuration for Deepseek:

```json
{
  "$schema": "https://charm.land/crush.json",
  "providers": {
    "deepseek": {
      "type": "openai-compat",
      "base_url": "https://api.deepseek.com/v1",
      "api_key": "$DEEPSEEK_API_KEY",
      "models": {
        "large": ["deepseek-chat"],
        "small": ["deepseek-coder"]
      }
    }
  }
}
```

Source: [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)

## Model String Parsing

Crush supports flexible model string formats. The model string can be specified in several ways:

| Format | Example | Description |
|--------|---------|-------------|
| Simple model name | `gpt-4o` | Searches all providers |
| Provider prefix | `openai/gpt-4o` | Uses specific provider |
| Nested model ID | `moonshot/kimi-k2` | Model ID contains slashes |
| Full qualified | `synthetic/moonshot/kimi-k2` | Provider with nested model ID |

### Parsing Logic

The `parseModelStr` function handles model string parsing:

```go
func parseModelStr(providers map[string]config.ProviderConfig, modelStr string) (providerFilter, modelID string) {
    parts := strings.Split(modelStr, "/")
    if len(parts) == 1 {
        return "", parts[0]
    }
    // Check if the first part is a valid provider name
    if _, ok := providers[parts[0]]; ok {
        return parts[0], strings.Join(parts[1:], "/")
    }
    // First part is not a valid provider, treat entire string as model ID
    return "", modelStr
}
```

Source: [internal/app/provider.go:16-32](https://github.com/charmbracelet/crush/blob/main/internal/app/provider.go#L16-L32)

## Model Selection and Lookup

### Finding Models Across Providers

The `findModels` function searches for models across configured providers:

```go
func findModels(providers map[string]config.ProviderConfig, largeModel, smallModel string) ([]modelMatch, []modelMatch, error) {
    largeProviderFilter, largeModelID := parseModelStr(providers, largeModel)
    smallProviderFilter, smallModelID := parseModelStr(providers, smallModel)
    // Validation and matching logic...
}
```

Source: [internal/app/provider.go:47-58](https://github.com/charmbracelet/crush/blob/main/internal/app/provider.go#L47-L58)

### Model Selection Tests

The test suite validates various model string formats:

| Test Case | Input | Expected Provider | Expected Model ID |
|-----------|-------|-------------------|-------------------|
| Simple model | `gpt-4o` | `openai` | `gpt-4o` |
| Model with slashes | `moonshot/kimi-k2` | `synthetic` | `moonshot/kimi-k2` |
| Provider and nested model | `synthetic/moonshot/kimi-k2` | `synthetic` | `moonshot/kimi-k2` |
| Invalid provider | `nonexistent-provider/gpt-4o` | Error | not found |

Source: [internal/app/provider_test.go:18-45](https://github.com/charmbracelet/crush/blob/main/internal/app/provider_test.go#L18-L45)

## OAuth-Based Providers

Some providers use OAuth authentication instead of API keys.

### Hyper (Device Flow)

Hyper uses OAuth with device flow for authentication:

```go
func (c *OAuthClient) Exchange(ctx context.Context, deviceCode,绑绑绑绑) (*Token, error)
```

Source: [internal/oauth/hyper/device.go](https://github.com/charmbracelet/crush/blob/main/internal/oauth/hyper/device.go)

### GitHub Copilot (Token Import)

Copilot integration supports importing existing tokens:

```go
func (c *Client) ImportCopilot(ctx context.Context, id string) (*oauth.Token, bool, error)
```

Source: [internal/client/config.go:68-84](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go#L68-L84)

## Provider API Endpoints

The backend exposes REST endpoints for provider management:

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/workspaces/{id}/config/set` | POST | Set a configuration field |
| `/workspaces/{id}/config/providers` | GET | Get workspace providers |
| `/workspaces/{id}/config/import-copilot` | POST | Import Copilot token |

Source: [internal/server/config.go:15-45](https://github.com/charmbracelet/crush/blob/main/internal/server/config.go#L15-L45)

## Provider Configuration Flow

```mermaid
graph TD
    A[Load crush.json] --> B{Config Level}
    B -->|.crush.json| C[Project Local]
    B -->|crush.json| D[Project Root]
    B -->|$HOME/.config/crush/crush.json| E[Global]
    C --> F[Merge Configs]
    D --> F
    E --> F
    F --> G[Resolve API Keys]
    G --> H[Validate Providers]
    H --> I[Provider Ready]
```

## Troubleshooting

### OpenRouter Authentication Issues

Users have reported issues with OpenRouter API key authentication:

> "I been trying to use OR models by setting up and exporting a proper API key as `OPENROUTER_API_KEY`, but I'm still getting errors `ERROR: Unauthorized`"

Ensure that:
1. The `OPENROUTER_API_KEY` environment variable is properly set
2. The API key has sufficient permissions
3. The provider configuration uses `openai-compat` type

Source: [GitHub Issue #2928](https://github.com/charmbracelet/crush/issues/2928)

### Provider Not Found

If a model is not found, verify:
1. The provider is properly configured in `crush.json`
2. The provider name in the model string matches the configuration
3. The model ID is valid for the provider

### DeepSeek API Issues

A regression in v0.68.0 caused DeepSeek requests to return 400 (Bad Request). This was fixed in v0.69.0. If experiencing issues, ensure you're running the latest version.

Source: [v0.69.0 Release Notes](https://github.com/charmbracelet/crush/releases/tag/v0.69.0)

## See Also

- [Configuration Reference](README.md#configuration) - Full configuration documentation
- [LSP Setup](README.md#lsps) - Language Server Protocol integration
- [Permissions System](internal/backend/events.go) - Tool permission management
- [Community Providers](https://github.com/charmbracelet/catwalk) - Additional Crush-compatible models

---

<a id='builtin-tools'></a>

## Built-in Tools

### Related Pages

Related topics: [Permission System](#permission-system), [MCP Integration](#mcp-integration)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [internal/agent/tools/tools.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/tools.go)
- [internal/agent/tools/bash.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/bash.go)
- [internal/agent/tools/read.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/read.go)
- [internal/agent/tools/write.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/write.go)
- [internal/agent/tools/edit.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/edit.go)
- [internal/agent/tools/grep.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/grep.go)
- [internal/agent/tools/glob.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/glob.go)
- [internal/agent/tools/view.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/view.go)
- [internal/agent/tools/download.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/download.go)
- [internal/agent/tools/fetch.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/fetch.go)
</details>

# Built-in Tools

Crush provides a comprehensive suite of built-in tools that enable the AI agent to interact with the filesystem, execute shell commands, and fetch external resources. These tools form the foundational capabilities that allow the model to read, write, edit, search, and execute code within your development environment.

## Overview

Built-in tools are registered capabilities exposed to the language model during agent execution. They provide controlled access to system operations with built-in permission checks, error handling, and security boundaries. The tool system is designed to be extensible, allowing the model to request tool executions with specific parameters while maintaining system safety through permission enforcement.

The tool architecture follows a unified interface pattern where each tool implements common interfaces for metadata, execution, and permission requirements. This design enables the agent coordinator to manage tool lifecycle, track usage, and enforce security policies consistently across all available tools.

```mermaid
graph TD
    A[Agent Coordinator] --> B[Tool Registry]
    B --> C[File Tools]
    B --> D[Shell Tools]
    B --> E[Network Tools]
    C --> C1[Read]
    C --> C2[Write]
    C --> C3[Edit]
    C --> C4[View]
    C --> C5[Grep]
    C --> C6[Glob]
    D --> D1[Bash]
    E --> E1[Fetch]
    E --> E2[Download]
    F[Permission Service] -.-> B
    G[Shell Executor] -.-> D1
```

## Tool Categories

### File System Tools

File system tools provide the agent with the ability to read, write, and modify files within the workspace. These tools handle various file operations including content reading, writing new files, editing existing files, viewing file contents with syntax highlighting, and searching for files by patterns.

**Available File System Tools:**

| Tool | Purpose |
|------|---------|
| `Read` | Read file contents with optional line range specification |
| `Write` | Write or overwrite entire file contents |
| `Edit` | Apply targeted modifications to specific file sections |
| `View` | Display file contents with formatting and line numbers |
| `Grep` | Search file contents using pattern matching |
| `Glob` | Find files matching glob patterns |

### Shell Execution Tools

Shell execution tools enable the agent to run bash commands and scripts within the development environment. The bash tool provides direct shell access with configurable permissions and security boundaries.

**Security Considerations:**

The bash tool executes commands through the system's shell interpreter. Security restrictions are in place to prevent execution of potentially harmful commands. Users have requested the ability to remove these security limits for unrestricted terminal access in certain workflows. Source: [internal/agent/tools/bash.go]()

The bash tool has been improved to handle interactive commands properly. A recent fix prevents the tool from hanging when the model attempts to run interactive commands like `git rebase -i`. Source: [v0.74.1 Release Notes]()

### Network Tools

Network tools allow the agent to fetch external resources and download files from URLs.

| Tool | Purpose |
|------|---------|
| `Fetch` | Retrieve content from HTTP/HTTPS URLs |
| `Download` | Download files from URLs to local filesystem |

## Tool Interface

All built-in tools implement the `Tool` interface defined in the tools package. This interface ensures consistent behavior across all tool implementations and enables the agent system to manage tools uniformly.

### Core Interface Components

Each tool provides:

- **Name**: Unique identifier used in tool calls
- **Description**: Human-readable explanation of tool purpose
- **Parameters**: Schema defining required and optional inputs
- **Execute Function**: Implementation logic for the tool operation

Source: [internal/agent/tools/tools.go]()

### Tool Registration

Tools are registered with the tool registry during application initialization. The registry maintains a mapping of tool names to tool instances and provides lookup functionality for the agent coordinator.

```mermaid
sequenceDiagram
    participant App as Application
    participant Registry as Tool Registry
    participant Tool as Individual Tool
    App->>Registry: Register Tool Instance
    Registry->>Tool: Initialize Tool
    Note over Registry: Store Tool in Map
    App->>Registry: Get Tool by Name
    Registry-->>App: Return Tool Instance
```

## File Reading Tool

The Read tool provides controlled access to file contents. It supports reading specific line ranges and handles various file encodings.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `file_path` | string | Yes | Path to the file to read |
| `offset` | integer | No | Starting line number (0-indexed) |
| `limit` | integer | No | Maximum number of lines to read |

**Behavior:**

- Returns file contents with line numbers for easy reference
- Supports partial reads via offset and limit parameters
- Handles binary files gracefully with appropriate error messages

Source: [internal/agent/tools/read.go]()

## File Writing Tool

The Write tool creates new files or overwrites existing ones with provided content.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `file_path` | string | Yes | Destination file path |
| `content` | string | Yes | Content to write to the file |

**Behavior:**

- Creates parent directories if they don't exist
- Overwrites existing files completely
- Returns confirmation with file path and character count

Source: [internal/agent/tools/write.go]()

## File Editing Tool

The Edit tool applies targeted modifications to existing files using a `str_replace` format that specifies exact locations for replacement.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `file_path` | string | Yes | File to edit |
| `old_string` | string | Yes | Exact text to replace |
| `new_string` | string | Yes | Replacement text |

**Behavior:**

- Performs exact string matching for replacement
- Requires precise `old_string` specification
- Only replaces the first matching occurrence by default

Source: [internal/agent/tools/edit.go]()

## File Viewing Tool

The View tool displays file contents with formatting suitable for user display. It provides a more visually oriented view compared to the Read tool.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `file_path` | string | Yes | Path to the file to view |
| `offset` | integer | No | Starting line number |
| `limit` | integer | No | Maximum lines to display |

Source: [internal/agent/tools/view.go]()

## Search Tools

### Grep Tool

The Grep tool searches file contents for patterns matching specified criteria.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `pattern` | string | Yes | Regular expression pattern |
| `path` | string | Yes | Directory or file to search |
| `file_pattern` | string | No | File glob pattern filter |
| `include_hidden` | boolean | No | Include hidden files (default: false) |

**Behavior:**

- Uses regular expression matching for pattern searching
- Optionally filters by file patterns
- Returns matching lines with context

Source: [internal/agent/tools/grep.go]()

### Glob Tool

The Glob tool finds files matching glob patterns within the workspace.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `pattern` | string | Yes | Glob pattern (e.g., `**/*.go`) |
| `path` | string | No | Base directory for search |

**Behavior:**

- Supports standard glob patterns including `**` for recursive matching
- Returns list of matching file paths
- Searches within workspace boundaries

Source: [internal/agent/tools/glob.go]()

## Bash Tool

The Bash tool executes shell commands in the development environment. It provides direct access to system shell capabilities while maintaining permission controls.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `command` | string | Yes | Shell command to execute |
| `timeout` | integer | No | Execution timeout in seconds |

**Behavior:**

- Executes commands through the system shell (`/bin/sh`)
- Captures stdout and stderr output
- Returns exit code and execution duration
- Commands run within the workspace directory context

Source: [internal/agent/tools/bash.go]()

**Security Model:**

The bash tool operates under the configured permission mode. Users can configure `permissions.yolo` mode to auto-approve all bash commands without prompting, or use safe mode for explicit approval of each command execution.

## Network Tools

### Fetch Tool

The Fetch tool retrieves content from HTTP/HTTPS URLs.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `url` | string | Yes | URL to fetch |
| `headers` | object | No | Custom HTTP headers |

**Behavior:**

- Supports GET requests by default
- Can include custom headers for authentication
- Returns response body and metadata

Source: [internal/agent/tools/fetch.go]()

### Download Tool

The Download tool fetches content from URLs and saves to the local filesystem.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `url` | string | Yes | URL to download |
| `file_path` | string | Yes | Destination file path |

**Behavior:**

- Downloads content and writes to specified path
- Creates parent directories as needed
- Returns file metadata on success

Source: [internal/agent/tools/download.go]()

## Permission System

Built-in tools interact with Crush's permission system to enforce security boundaries. Each tool declares its permission requirements, and the permission service validates execution requests.

### Permission Modes

| Mode | Behavior |
|------|----------|
| `safe` | Prompts for confirmation before each tool execution |
| `yolo` | Auto-approves all tool executions |

**Configuration Location:** `crush.json` → `permissions.yolo`

Source: [Configuration Documentation](README.md)

**Community Request:** Users have requested the ability to switch between permission modes at runtime without restarting the session. This feature is tracked in [GitHub Issue #2935](https://github.com/charmbracelet/crush/issues/2935).

## Tool Execution Flow

```mermaid
sequenceDiagram
    participant Model as Language Model
    participant Coordinator as Agent Coordinator
    participant Registry as Tool Registry
    participant Tool as Tool Implementation
    participant Perms as Permission Service
    participant Shell as Shell Executor
    
    Model->>Coordinator: Request Tool Execution
    Coordinator->>Registry: Lookup Tool
    Registry-->>Coordinator: Return Tool Instance
    Coordinator->>Perms: Check Permission
    Perms-->>Coordinator: Approved/Denied
    Coordinator->>Tool: Execute with Params
    Tool->>Shell: Run System Operations
    Shell-->>Tool: Operation Result
    Tool-->>Coordinator: Tool Result
    Coordinator-->>Model: Return Result
```

## Error Handling

Built-in tools implement consistent error handling patterns:

- **File Not Found**: Returns descriptive error with path information
- **Permission Denied**: Triggers permission request flow
- **Timeout**: Returns partial results with timeout indicator
- **Invalid Parameters**: Returns validation error with parameter details

All tool errors are wrapped with context information to aid debugging and logging.

## Configuration

Tools can be configured through the Crush configuration file:

```json
{
  "$schema": "https://charm.land/crush.json",
  "tools": {
    "bash": {
      "timeout": 60
    }
  }
}
```

## See Also

- [Agent Architecture](../architecture/agent.md)
- [Permission System](../security/permissions.md)
- [Configuration Reference](../configuration/index.md)
- [Shell Integration](../shells/index.md)

---

<a id='permission-system'></a>

## Permission System

### Related Pages

Related topics: [Built-in Tools](#builtin-tools), [Configuration Guide](#configuration-guide)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [internal/server/proto.go](https://github.com/charmbracelet/crush/blob/main/internal/server/proto.go)
- [internal/backend/permission.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/permission.go)
- [internal/backend/backend.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/backend.go)
- [internal/server/events.go](https://github.com/charmbracelet/crush/blob/main/internal/server/events.go)
- [internal/server/e2e_test.go](https://github.com/charmbracelet/crush/blob/main/internal/server/e2e_test.go)
- [internal/client/proto.go](https://github.com/charmbracelet/crush/blob/main/internal/client/proto.go)
- [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)
</details>

# Permission System

## Overview

The Permission System in Crush provides a security layer that controls which tools and operations the AI agent can execute. It acts as an intermediary between the agent's intent and actual execution, requiring user confirmation for sensitive operations while providing flexibility through configurable modes.

The system is designed around a client-server architecture where permissions are managed per-workspace and can be controlled through both the HTTP API and configuration settings.

## Architecture

The permission system follows a multi-layer architecture:

```mermaid
graph TD
    A[AI Agent] --> B[Tool Execution Request]
    B --> C{Permission Mode}
    C -->|Yolo| D[Auto-Approve]
    C -->|Safe| E[Request Permission]
    E --> F[SSE Broadcast]
    F --> G[Client A]
    F --> H[Client B]
    G --> I{User Decision}
    H --> I
    I -->|Allow| J[Execute Tool]
    I -->|Deny| K[Reject Tool]
    D --> J
    J --> L[Return Result]
    K --> L
```

### Components

| Component | File | Responsibility |
|-----------|------|-----------------|
| Permission Manager | `internal/permission/permission.go` | Core permission logic and state management |
| Backend Handlers | `internal/backend/permission.go` | Workspace-level permission operations |
| API Controllers | `internal/server/proto.go` | HTTP endpoint handlers for permission APIs |
| Client API | `internal/client/proto.go` | Client-side permission request methods |
| Event Broadcasting | `internal/server/events.go` | SSE event distribution for permission notifications |

## Permission Modes

Crush supports two primary permission modes configured via `crush.json`:

### Yolo Mode

In Yolo mode, all tool executions are automatically approved without user interaction. This is suitable for trusted environments or automated workflows.

```json
{
  "permissions": {
    "yolo": true
  }
}
```

### Safe Mode (Default)

In Safe mode, the system prompts for user confirmation before executing tools. Permission requests are broadcast to all connected clients via Server-Sent Events (SSE), allowing any client to grant or deny the request.

## API Endpoints

The permission system exposes the following REST endpoints:

### Set Skip Permissions

Sets whether permission prompts should be skipped for a workspace.

```
POST /workspaces/{id}/permissions/skip
```

| Parameter | Type | Location | Description |
|-----------|------|----------|-------------|
| `id` | string | path | Workspace ID |
| `Skip` | bool | body | Whether to skip permission prompts |

**Response Codes:**

| Code | Description |
|------|-------------|
| 200 | Success |
| 400 | Invalid request body |
| 404 | Workspace not found |
| 500 | Internal server error |

Source: [internal/server/proto.go:35-55](https://github.com/charmbracelet/crush/blob/main/internal/server/proto.go)

### Get Skip Permissions Status

Retrieves the current skip-permissions setting for a workspace.

```
GET /workspaces/{id}/permissions/skip
```

| Parameter | Type | Location | Description |
|-----------|------|----------|-------------|
| `id` | string | path | Workspace ID |

**Response:**

```json
{
  "Skip": true
}
```

Source: [internal/server/proto.go:77-91](https://github.com/charmbracelet/crush/blob/main/internal/server/proto.go)

### Grant Permission

Grants or denies a pending permission request.

```
POST /workspaces/{id}/permissions/grant
```

**Request Body:**

```json
{
  "permission": {
    "id": "req-123",
    "tool_call_id": "call-456",
    "tool_name": "view",
    "description": "read a file",
    "action": "read",
    "path": "/path/to/file"
  },
  "action": "allow"
}
```

Source: [internal/server/proto.go:16-34](https://github.com/charmbracelet/crush/blob/main/internal/server/proto.go)

## Backend Implementation

### Workspace Permission Methods

The backend provides methods for managing workspace-level permission settings:

```go
// SetPermissionsSkip sets whether permission prompts are skipped.
func (b *Backend) SetPermissionsSkip(workspaceID string, skip bool) error

// GetPermissionsSkip returns whether permission prompts are skipped.
func (b *Backend) GetPermissionsSkip(workspaceID string) (bool, error)
```

Source: [internal/backend/permission.go:85-100](https://github.com/charmbracelet/crush/blob/main/internal/backend/permission.go)

### Permission Request Flow

When a tool execution requires permission:

1. The agent initiates a permission request through `App.Permissions.Request()`
2. The request is published to all connected SSE subscribers
3. Any client can respond with a grant or denial
4. The first grant resolves the pending request

```go
granted, err := h.app.Permissions.Request(ctx, permission.CreatePermissionRequest{
    SessionID:   sessionID,
    ToolCallID:  toolCallID,
    ToolName:    "view",
    Description: "read a file",
    Action:      "read",
    Path:        h.workspace.Path,
})
```

Source: [internal/server/e2e_test.go:180-190](https://github.com/charmbracelet/crush/blob/main/internal/server/e2e_test.go)

## Client SDK

The client provides methods for interacting with the permission system:

### SetPermissionsSkipRequests

Enables or disables permission prompts for a workspace.

```go
func (c *Client) SetPermissionsSkipRequests(ctx context.Context, id string, skip bool) error
```

Source: [internal/client/proto.go:180-190](https://github.com/charmbracelet/crush/blob/main/internal/client/proto.go)

### GetPermissionsSkipRequests

Retrieves the current permission skip setting.

```go
func (c *Client) GetPermissionsSkipRequests(ctx context.Context, id string) (bool, error)
```

Source: [internal/client/proto.go:192-208](https://github.com/charmbracelet/crush/blob/main/internal/client/proto.go)

## Event System

Permission events are broadcast to all connected clients via SSE:

```mermaid
sequenceDiagram
    participant Agent
    participant Server
    participant ClientA
    participant ClientB
    
    Agent->>Server: Tool Execution Request
    Server->>ClientA: PermissionRequest Event
    Server->>ClientB: PermissionRequest Event
    ClientA->>Server: Grant Permission
    Server->>Agent: Resolution (resolved=true)
    Server->>ClientB: PermissionNotification Event
```

### Event Types

| Event Type | Payload | Description |
|------------|---------|-------------|
| `PermissionRequest` | Tool details, action, path | Request for permission to execute |
| `PermissionNotification` | ToolCallID, Granted, Denied | Notification of permission decision |
| `PermissionGrant` | Resolution status | Response to permission request |

Source: [internal/server/events.go:60-70](https://github.com/charmbracelet/crush/blob/main/internal/server/events.go)

## Error Handling

The API handles errors by mapping them to appropriate HTTP status codes:

```go
func (c *controllerV1) handleError(w http.ResponseWriter, r *http.Request, err error) {
    // A canceled agent run is not an error from the prompting
    // client's perspective. The cancellation reaches every SSE
    // subscriber via the FinishReasonCanceled marker on the assistant
    // message; the still-open POST should not surface a 500.
    if errors.Is(err, context.Canceled) {
        w.WriteHeader(http.StatusOK)
        return
    }
    status := http.StatusInternalServerError
    // ... error mapping logic
}
```

Source: [internal/server/proto.go:93-105](https://github.com/charmbracelet/crush/blob/main/internal/server/proto.go)

### Common Errors

| Error | Constant | Description |
|-------|----------|-------------|
| Workspace not found | `ErrWorkspaceNotFound` | Specified workspace does not exist |
| Invalid permission action | `ErrInvalidPermissionAction` | Action is not allow/deny |

Source: [internal/backend/backend.go:32-43](https://github.com/charmbracelet/crush/blob/main/internal/backend/backend.go)

## Configuration

Permissions can be configured at three levels with the following priority:

1. `.crush.json` (project level)
2. `crush.json` (project level)
3. `$HOME/.config/crush/crush.json` (global)

Example configuration:

```json
{
  "$schema": "https://charm.land/crush.json",
  "permissions": {
    "yolo": false
  }
}
```

Source: [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)

## Community Considerations

### Feature Request: Multiple Permission Profiles

Issue [#2935](https://github.com/charmbracelet/crush/issues/2935) requests the ability to switch between permission profiles at runtime:

- **Yolo Mode**: Auto-approve all tool calls
- **Safe Mode**: Require confirmation for each tool call
- **Custom Mode**: Per-tool permission rules

Currently, the `permissions.yolo` setting applies globally for the entire session and cannot be changed without restarting Crush.

### Bash Command Security

Issue [#928](https://github.com/charmbracelet/crush/issues/928) discusses security restrictions on bash commands. Users have requested the ability to remove security limits for unrestricted terminal access, though this involves trade-offs with the permission system's safety guarantees.

## Testing

The permission system includes end-to-end tests that verify:

- Permission requests are properly broadcast to all clients
- The first grant resolves pending requests with `resolved=true`
- Subsequent grants do not affect already-resolved requests
- Permission notifications are delivered to all clients

```go
// First grant must report resolved=true
resolvedA := h.grantPermission(t, ctx, h.workspace.ID, proto.PermissionGrant{
    Permission: reqEv.Payload,
    Action:     proto.PermissionAllow,
})
require.True(t, resolvedA, "client A's grant must resolve the pending request")
```

Source: [internal/server/e2e_test.go:198-204](https://github.com/charmbracelet/crush/blob/main/internal/server/e2e_test.go)

---

<a id='mcp-integration'></a>

## MCP Integration

### Related Pages

Related topics: [Built-in Tools](#builtin-tools), [Skills System](#skills-system)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [internal/server/config.go](https://github.com/charmbracelet/crush/blob/main/internal/server/config.go)
- [internal/backend/config.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/config.go)
- [internal/server/events.go](https://github.com/charmbracelet/crush/blob/main/internal/server/events.go)
- [internal/client/config.go](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go)
- [internal/client/proto.go](https://github.com/charmbracelet/crush/blob/main/internal/client/proto.go)
- [internal/backend/events.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/events.go)
- [internal/commands/commands.go](https://github.com/charmbracelet/crush/blob/main/internal/commands/commands.go)
- [internal/app/app.go](https://github.com/charmbracelet/crush/blob/main/internal/app/app.go)
</details>

# MCP Integration

The Model Context Protocol (MCP) integration in Crush enables the AI agent to interact with external MCP-compatible servers, extending its capabilities through tools, prompts, and resources exposed by those servers. This integration allows Crush to serve as a unified interface for multiple MCP servers within a workspace, providing seamless access to external functionality.

## Architecture Overview

The MCP integration follows a client-server architecture where Crush acts as a central hub managing connections to multiple MCP servers. The system is designed to handle concurrent MCP clients, each potentially exposing different tools, prompts, and resources.

```mermaid
graph TD
    A[Crush Workspace] --> B[Workspace MCP Manager]
    B --> C[MCP Client 1]
    B --> D[MCP Client 2]
    B --> N[... More Clients]
    C --> E[Tools]
    C --> F[Prompts]
    C --> G[Resources]
    D --> H[Tools]
    D --> I[Prompts]
    D --> J[Resources]
```

### Core Components

| Component | Package | Purpose |
|-----------|---------|---------|
| MCP Manager | `internal/agent/tools/mcp` | Manages MCP client lifecycle and tool registration |
| Server Handlers | `internal/server/config.go` | REST API endpoints for MCP operations |
| Backend Logic | `internal/backend/config.go` | Business logic for MCP interactions |
| Client Interface | `internal/client/config.go` | HTTP client for MCP server communication |
| Event System | `internal/server/events.go` | Real-time MCP state event broadcasting |

Source: [internal/app/app.go:42-50](https://github.com/charmbracelet/crush/blob/main/internal/app/app.go#L42-L50)

## MCP Client States

Each MCP client maintains a state machine that tracks its connection lifecycle. The integration supports the following states:

```mermaid
stateDiagram-v2
    [*] --> Disconnected
    Disconnected --> Connecting
    Connecting --> Connected
    Connected --> Disconnecting
    Disconnecting --> Disconnected
    Connecting --> Error
    Connected --> Error
    Error --> Connecting
```

### MCPClientInfo Data Model

| Field | Type | Description |
|-------|------|-------------|
| `Name` | `string` | Unique identifier for the MCP server |
| `State` | `MCPState` | Current connection state |
| `Error` | `string` | Last error message if in error state |
| `ToolCount` | `int` | Number of tools exposed by the server |
| `PromptCount` | `int` | Number of prompts available |
| `ResourceCount` | `int` | Number of resources accessible |
| `ConnectedAt` | `timestamp` | When the connection was established |

Source: [internal/server/config.go:10-18](https://github.com/charmbracelet/crush/blob/main/internal/server/config.go#L10-L18)

## MCP Event System

The integration uses an event-driven architecture to broadcast MCP state changes and updates to subscribed clients. Events are published through the central pubsub broker.

### Supported Event Types

| Event Type | Trigger |
|------------|---------|
| `MCPEventStateChanged` | Client connection state changed |
| `MCPEventToolsListChanged` | Available tools updated |
| `MCPEventPromptsListChanged` | Available prompts updated |
| `MCPEventResourcesListChanged` | Available resources updated |

Source: [internal/server/events.go:120-131](https://github.com/charmbracelet/crush/blob/main/internal/server/events.go#L120-L131)

The event conversion function maps internal MCP event types to protocol buffer equivalents:

```go
func mcpEventTypeToProto(t mcp.EventType) proto.MCPEventType {
    switch t {
    case mcp.EventStateChanged:
        return proto.MCPEventStateChanged
    case mcp.EventToolsListChanged:
        return proto.MCPEventToolsListChanged
    // ... other mappings
    }
}
```

Source: [internal/server/events.go:137-149](https://github.com/charmbracelet/crush/blob/main/internal/server/events.go#L137-L149)

## REST API Endpoints

The MCP functionality is exposed through a set of REST endpoints under the `/workspaces/{id}/mcp` path.

### Endpoint Reference

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/workspaces/{id}/mcp/states` | Get states of all MCP clients |
| `POST` | `/workspaces/{id}/mcp/refresh-tools` | Refresh tools for a named server |
| `POST` | `/workspaces/{id}/mcp/refresh-prompts` | Refresh prompts for a named server |
| `POST` | `/workspaces/{id}/mcp/refresh-resources` | Refresh resources for a named server |
| `POST` | `/workspaces/{id}/mcp/get-prompt` | Retrieve a prompt from a server |
| `POST` | `/workspaces/{id}/mcp/read-resource` | Read a resource from a server |
| `POST` | `/workspaces/{id}/mcp/docker/enable` | Enable Docker MCP server |
| `POST` | `/workspaces/{id}/mcp/docker/disable` | Disable Docker MCP server |

Source: [internal/server/config.go:45-140](https://github.com/charmbracelet/crush/blob/main/internal/server/config.go#L45-L140)

### Get MCP Client States

```go
func (c *controllerV1) handleGetWorkspaceMCPStates(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    states := c.backend.MCPGetStates(id)
    result := make(map[string]proto.MCPClientInfo, len(states))
    for k, v := range states {
        result[k] = proto.MCPClientInfo{
            Name:          v.Name,
            State:         proto.MCPState(v.State),
            Error:         v.Error,
            ToolCount:     v.Counts.Tools,
            PromptCount:   v.Counts.Prompts,
            ResourceCount: v.Counts.Resources,
            ConnectedAt:   v.ConnectedAt,
        }
    }
    jsonEncode(w, result)
}
```

Source: [internal/server/config.go:6-25](https://github.com/charmbracelet/crush/blob/main/internal/server/config.go#L6-L25)

## Tools Integration

MCP tools are exposed to the AI agent through the command system. Each tool from an MCP server is registered as a callable command with its schema exposed.

### Tool Refresh Flow

```mermaid
sequenceDiagram
    participant Client
    participant Server
    participant Backend
    participant MCPTools
    Client->>Server: POST /mcp/refresh-tools
    Server->>Backend: RefreshMCPTools(workspaceID, name)
    Backend->>MCPTools: RefreshTools(ctx, cfg, name)
    MCPTools-->>Backend: Updated tool list
    Backend-->>Server: Success
    Server-->>Client: 200 OK
```

Source: [internal/backend/config.go:80-86](https://github.com/charmbracelet/crush/blob/main/internal/backend/config.go#L80-L86)

### Backend Tool Refresh Implementation

```go
// RefreshMCPTools refreshes the tools for a named MCP server.
func (b *Backend) RefreshMCPTools(ctx context.Context, workspaceID, name string) error {
    ws, err := b.GetWorkspace(workspaceID)
    if err != nil {
        return err
    }
    mcptools.RefreshTools(ctx, ws.Cfg, name)
    return nil
}
```

Source: [internal/backend/config.go:80-86](https://github.com/charmbracelet/crush/blob/main/internal/backend/config.go#L80-L86)

## Prompts Integration

MCP prompts allow external servers to define reusable prompt templates that can be invoked by the agent. Prompts can accept arguments defined in their schema.

### Prompt Request Model

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ClientID` | `string` | Yes | The MCP client name |
| `PromptID` | `string` | Yes | The prompt identifier |
| `Args` | `map[string]string` | No | Prompt arguments |

Source: [internal/client/config.go:1-15](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go#L1-L15)

### Get Prompt Implementation

```go
func (c *Client) GetMCPPrompt(ctx context.Context, id, clientID, promptID string, args map[string]string) (string, error) {
    rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/mcp/get-prompt", id), nil, jsonBody(struct {
        ClientID string            `json:"client_id"`
        PromptID string            `json:"prompt_id"`
        Args     map[string]string `json:"args"`
    }{ClientID: clientID, PromptID: promptID, Args: args}), http.Header{"Content-Type": []string{"application/json"}})
    // ... error handling
    return result.Prompt, nil
}
```

Source: [internal/client/config.go:80-95](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go#L80-L95)

### Prompt Command Building

Prompts are exposed as commands through the command system, enabling the AI agent to invoke them:

```go
commands = append(commands, MCPPrompt{
    ID:          key,
    Title:       prompt.Title,
    Description: prompt.Description,
    PromptID:    prompt.Name,
    ClientID:    mcpName,
    Arguments:   args,
})
```

Source: [internal/commands/commands.go:180-187](https://github.com/charmbracelet/crush/blob/main/internal/commands/commands.go#L180-L187)

## Resources Integration

MCP resources provide access to external data that can be read by the agent. Resources are identified by URIs and can return text or binary content.

### Resource Contents Model

```go
type MCPResourceContents struct {
    URI      string `json:"uri"`
    MIMEType string `json:"mime_type,omitempty"`
    Text     string `json:"text,omitempty"`
    Blob     []byte `json:"blob,omitempty"`
}
```

Source: [internal/client/config.go:60-65](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go#L60-L65)

### Read Resource Implementation

```go
func (c *Client) ReadMCPResource(ctx context.Context, id, name, uri string) ([]MCPResourceContents, error) {
    // ... HTTP request setup
    var contents []MCPResourceContents
    if err := json.NewDecoder(rsp.Body).Decode(&contents); err != nil {
        return nil, fmt.Errorf("failed to decode MCP resource: %w", err)
    }
    return contents, nil
}
```

Source: [internal/client/config.go:67-78](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go#L67-L78)

### Server-side Resource Reading

```go
func (c *controllerV1) handlePostWorkspaceMCPReadResource(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    var req proto.MCPReadResourceRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        // error handling
        return
    }
    contents, err := c.backend.ReadMCPResource(r.Context(), id, req.Name, req.URI)
    if err != nil {
        c.handleError(w, r, err)
        return
    }
    jsonEncode(w, contents)
}
```

Source: [internal/server/config.go:103-118](https://github.com/charmbracelet/crush/blob/main/internal/server/config.go#L103-L118)

## Docker MCP Support

Crush includes built-in support for Docker-based MCP servers, allowing users to easily enable or disable Docker MCP integration for their workspace.

### Docker MCP Operations

| Operation | Endpoint | Description |
|-----------|----------|-------------|
| Enable | `POST /workspaces/{id}/mcp/docker/enable` | Enable Docker MCP server |
| Disable | `POST /workspaces/{id}/mcp/docker/disable` | Disable Docker MCP server |

### Enable Docker MCP

```go
func (c *Client) EnableDockerMCP(ctx context.Context, id string) error {
    rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/mcp/docker/enable", id), nil, nil, nil)
    if err != nil {
        return fmt.Errorf("failed to enable docker MCP: %w", err)
    }
    defer rsp.Body.Close()
    if rsp.StatusCode != http.StatusOK {
        return fmt.Errorf("failed to enable docker MCP: status code %d", rsp.StatusCode)
    }
    return nil
}
```

Source: [internal/client/config.go:67-77](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go#L67-L77)

## Client-Side Operations

The `Client` struct in `internal/client/proto.go` provides methods for managing MCP resources and prompts programmatically.

### Refresh Operations Reference

| Method | Parameters | Description |
|--------|------------|-------------|
| `MCPRefreshPrompts` | `ctx`, `id`, `name` | Refresh available prompts |
| `MCPRefreshResources` | `ctx`, `id`, `name` | Refresh available resources |
| `MCPGetStates` | `ctx`, `id` | Get all MCP client states |

Source: [internal/client/proto.go:200-230](https://github.com/charmbracelet/crush/blob/main/internal/client/proto.go#L200-L230)

### Refresh Prompts Implementation

```go
func (c *Client) MCPRefreshPrompts(ctx context.Context, id, name string) error {
    rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/mcp/refresh-prompts", id), nil,
        jsonBody(struct {
            Name string `json:"name"`
        }{Name: name}),
        http.Header{"Content-Type": []string{"application/json"}})
    if err != nil {
        return fmt.Errorf("failed to refresh MCP prompts: %w", err)
    }
    defer rsp.Body.Close()
    if rsp.StatusCode != http.StatusOK {
        return fmt.Errorf("failed to refresh MCP prompts: status code %d", rsp.StatusCode)
    }
    return nil
}
```

Source: [internal/client/proto.go:200-215](https://github.com/charmbracelet/crush/blob/main/internal/client/proto.go#L200-L215)

## Event Subscription

The application subscribes to MCP events and re-publishes them through the central event broker, enabling UI components and other subsystems to react to MCP state changes.

### Event Subscription Setup

```go
func setupSubscriber[T any](
    ctx context.Context,
    wg *sync.WaitGroup,
    name string,
    subscriber func(context.Context) <-chan pubsub.Event[T],
    broker *pubsub.Broker[tea.Msg],
) {
    wg.Go(func() {
        subCh := subscriber(ctx)
        for {
            select {
            case event, ok := <-subCh:
                if !ok {
                    slog.Debug("Subscription channel closed", "name", name)
                    return
                }
                broker.Publish(pubsub.UpdatedEvent, tea.Msg(event))
            case <-ctx.Done():
                slog.Debug("Subscription cancelled", "name", name)
                return
            }
        }
    })
}
```

Source: [internal/app/app.go:70-91](https://github.com/charmbracelet/crush/blob/main/internal/app/app.go#L70-L91)

### MCP Event Subscription Registration

```go
setupSubscriber(ctx, app.serviceEventsWG, "mcp", mcp.SubscribeEvents, app.events)
```

Source: [internal/app/app.go:44](https://github.com/charmbracelet/crush/blob/main/internal/app/app.go#L44)

## Backend Event Handling

The backend provides workspace-specific event subscriptions for MCP operations.

### SubscribeEvents Implementation

```go
func (b *Backend) SubscribeEvents(ctx context.Context, workspaceID string) (<-chan pubsub.Event[tea.Msg], error) {
    ws, err := b.GetWorkspace(workspaceID)
    if err != nil {
        return nil, err
    }
    return ws.Events(ctx), nil
}
```

Source: [internal/backend/events.go:16-24](https://github.com/charmbracelet/crush/blob/main/internal/backend/events.go#L16-L24)

## Error Handling

MCP operations follow a consistent error handling pattern throughout the codebase:

| Error Type | Response Code | Description |
|------------|---------------|-------------|
| Invalid request body | `400 Bad Request` | JSON decode failed |
| Workspace not found | `404 Not Found` | Workspace ID does not exist |
| Server error | `500 Internal Server Error` | Backend operation failed |

Source: [internal/server/config.go:26-36](https://github.com/charmbracelet/crush/blob/main/internal/server/config.go#L26-L36)

Example error handling pattern:

```go
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
    c.server.logError(r, "Failed to decode request", "error", err)
    jsonError(w, http.StatusBadRequest, "failed to decode request")
    return
}
```

Source: [internal/server/config.go:26-30](https://github.com/charmbracelet/crush/blob/main/internal/server/config.go#L26-L30)

## Data Flow Diagram

The following diagram illustrates the complete data flow for a typical MCP operation:

```mermaid
sequenceDiagram
    participant User as User/Agent
    participant UI as UI Layer
    participant Server as REST API Server
    participant Backend as Backend Logic
    participant MCPTools as MCP Tools Module
    participant MCPServer as External MCP Server

    User->>UI: Trigger MCP operation
    UI->>Server: HTTP Request
    Server->>Backend: Call backend method
    Backend->>MCPTools: Execute MCP operation
    MCPTools->>MCPServer: Send MCP protocol message
    MCPServer-->>MCPTools: MCP response
    MCPTools-->>Backend: Processed result
    Backend-->>Server: Response data
    Server-->>UI: JSON response
    UI-->>User: Display result
```

## Testing Considerations

When testing MCP integration components, the test infrastructure provides utilities for creating mock workspaces with client state tracking:

```go
func newTestWorkspace(t *testing.T, b *Backend, key string) (*Workspace, *atomic.Int32) {
    var shutdowns atomic.Int32
    ws := &Workspace{
        ID:           uuid.New().String(),
        Path:         key,
        resolvedPath: key,
        clients:      make(map[string]*clientState),
        shutdownFn:   func() { shutdowns.Add(1) },
    }
    b.mu.Lock()
    b.workspaces.Set(ws.ID, ws)
    b.pathIndex[key] = ws.ID
    b.mu.Unlock()
    return ws, &shutdowns
}
```

Source: [internal/backend/backend_test.go:25-38](https://github.com/charmbracelet/crush/blob/main/internal/backend/backend_test.go#L25-L38)

---

<a id='skills-system'></a>

## Skills System

### Related Pages

Related topics: [MCP Integration](#mcp-integration), [Configuration Guide](#configuration-guide)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [internal/skills/manager.go](https://github.com/charmbracelet/crush/blob/main/internal/skills/manager.go)
- [internal/skills/catalog.go](https://github.com/charmbracelet/crush/blob/main/internal/skills/catalog.go)
- [internal/skills/skills.go](https://github.com/charmbracelet/crush/blob/main/internal/skills/skills.go)
- [internal/skills/embed.go](https://github.com/charmbracelet/crush/blob/main/internal/skills/embed.go)
- [.agents/skills/builtin-skills/SKILL.md](https://github.com/charmbracelet/crush/blob/main/.agents/skills/builtin-skills/SKILL.md)
- [internal/skills/builtin/crush-config/SKILL.md](https://github.com/charmbracelet/crush/blob/main/internal/skills/builtin/crush-config/SKILL.md)
- [README.md](https://github.com/charmbracelet/crush/blob/main/README.md)
</details>

# Skills System

Crush's Skills System provides a powerful extensibility mechanism that allows the agent to discover, load, and invoke reusable skill packages. Skills are self-contained instructional modules that inform Crush's behavior, provide specialized knowledge, or enable specific workflows. The system follows the open [Agent Skills](https://agentskills.io) standard, making skills portable across compatible agents.

## Overview

The Skills System serves as a bridge between Crush's core reasoning capabilities and domain-specific expertise. When a skill is invoked, its instructions are loaded into the conversation context, allowing the model to leverage specialized knowledge for tasks within that skill's scope.

Skills can be triggered in two ways:

1. **Automatic Discovery**: Crush analyzes the current context and automatically activates relevant skills based on file types, project structure, or detected workflow patterns.
2. **Manual Invocation**: Users can explicitly invoke skills through the commands palette using `user:skill-name` for global skills or `project:skill-name` for project-local skills.

## Architecture

### Core Components

```mermaid
graph TD
    A[Skills Manager] --> B[Skill Catalog]
    A --> C[Skill Loader]
    A --> D[Event System]
    
    B --> E[Global Paths]
    B --> F[Project Paths]
    B --> G[Custom Paths]
    
    C --> H[SKILL.md Parser]
    C --> I[YAML Frontmatter]
    C --> J[Embedding Generator]
    
    D --> K[pubsub Broker]
    K --> L[UI Notifications]
    K --> M[Server Events]
```

### Skills Manager

The `SkillsManager` (defined in `internal/skills/manager.go`) is the central orchestrator responsible for:

- Scanning configured paths for available skills
- Maintaining the skill catalog with metadata
- Handling skill discovery and state tracking
- Publishing skill-related events to the application

**Key Responsibilities:**

| Responsibility | Description |
|----------------|-------------|
| Path Resolution | Resolves skill directories from multiple configured locations |
| Discovery | Discovers skills by scanning for `SKILL.md` files |
| Catalog Management | Maintains an indexed catalog of available skills |
| State Tracking | Monitors the discovery state of each skill |
| Event Publishing | Notifies subscribers of skill discovery and state changes |

### Skill Catalog

The `SkillCatalog` (in `internal/skills/catalog.go`) provides indexing and lookup capabilities:

```go
type SkillCatalog struct {
    skills    map[string]*Skill
    mu        sync.RWMutex
    rootPaths []string
}
```

The catalog supports:
- Fast lookup by skill name
- Filtering by discovery state
- Path-based grouping
- Thread-safe operations with `sync.RWMutex`

### Skill Data Model

Skills are represented by the `Skill` struct with the following structure:

| Field | Type | Description |
|-------|------|-------------|
| `Name` | `string` | Human-readable skill name |
| `Path` | `string` | Absolute path to the skill directory |
| `State` | `SkillDiscoveryState` | Current discovery state |
| `Err` | `error` | Error encountered during discovery |
| `Instructions` | `string` | Parsed content from `SKILL.md` |
| `Metadata` | `SkillMetadata` | YAML frontmatter configuration |

**Discovery States:**

| State | Value | Description |
|-------|-------|-------------|
| `StateUnknown` | `0` | Initial state before discovery |
| `StateDiscovering` | `1` | Currently scanning the skill directory |
| `StateDiscovered` | `2` | Skill found and parsed successfully |
| `StateError` | `3` | Error occurred during discovery |

### Skill Events

The skill system publishes events through the `pubsub` broker, allowing other components to react to skill state changes:

```go
type Event struct {
    Type  EventType
    States []SkillState
}
```

Event types include:
- `skills.EventUpdated`: Published when skill states change
- `skills.EventDiscovered`: Published when new skills are found

## Skill Discovery Paths

Crush searches for skills in a hierarchical set of locations, enabling both system-wide and project-specific skill deployment.

### Global Skill Paths

Skills are discovered from these global locations (in priority order):

| Platform | Path | Environment Variable |
|----------|------|---------------------|
| Unix | `$XDG_CONFIG_HOME/agents/skills` | - |
| Unix | `~/.config/agents/skills/` | - |
| Unix | `$XDG_CONFIG_HOME/crush/skills` | - |
| Unix | `~/.config/crush/skills/` | - |
| Unix | `~/.agents/skills/` | - |
| Unix | `~/.claude/skills/` | - |
| Windows | `%LOCALAPPDATA%\agents\skills\` | - |
| Windows | `%LOCALAPPDATA%\crush\skills\` | - |
| All | - | `$CRUSH_SKILLS_DIR` |
| Config | - | `options.skills_paths` |

### Project-Level Skill Paths

Crush also discovers skills within the current project directory:

| Relative Path | Prefix | Example Invocation |
|---------------|--------|-------------------|
| `.agents/skills` | `project:` | `project:my-skill` |
| `.crush/skills` | `project:` | `project:my-skill` |
| `.claude/skills` | `project:` | `project:my-skill` |
| `.cursor/skills` | `project:` | `project:my-skill` |

## Skill File Structure

### SKILL.md Format

Skills are defined by a `SKILL.md` file containing:

```markdown
---
name: my-skill
description: A skill that helps with specific tasks
---
# Skill Instructions

These instructions are loaded into the conversation context when the skill is invoked.
```

### YAML Frontmatter Schema

The YAML frontmatter supports the following fields:

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `name` | `string` | Directory name | Human-readable skill identifier |
| `description` | `string` | Empty | Brief description shown in command palette |
| `user-invocable` | `boolean` | `false` | Enables manual invocation via commands palette |
| `disable-model-invocation` | `boolean` | `false` | Prevents automatic model-triggered invocation |

### User-Invocable Skills

When `user-invocable: true` is set in the frontmatter, the skill appears in the commands palette with a prefix:

- **Global skills**: `user:skill-name`
- **Project skills**: `project:skill-name`

Example configuration:

```yaml
---
name: my-skill
description: A skill that can be invoked as a command
user-invocable: true
---
```

### Disabling Model Invocation

To prevent the model from automatically triggering a skill while allowing user invocation:

```yaml
---
name: restricted-skill
description: Only invocable by users
user-invocable: true
disable-model-invocation: true
---
```

Skills with `disable-model-invocation: true` won't appear in the model's available skills list but remain accessible through manual user invocation.

## Configuration

### Global Skills Paths

Add custom skill directories via the Crush configuration file:

```json
{
  "$schema": "https://charm.land/crush.json",
  "options": {
    "skills_paths": [
      "~/.config/crush/skills",
      "./project-skills"
    ]
  }
}
```

### Disabling Built-in Skills

The built-in `crush-config` skill can be disabled:

```json
{
  "$schema": "https://charm.land/crush.json",
  "options": {
    "disabled_skills": ["crush-config"]
  }
}
```

### Environment Variables

| Variable | Description |
|----------|-------------|
| `CRUSH_SKILLS_DIR` | Additional directory to search for skills |

## Built-in Skills

### Crush-Config

Crush ships with a built-in `crush-config` skill that enables self-configuration through natural language. Users can ask Crush to configure itself, and the skill provides instructions for common configuration tasks.

**Location:** `internal/skills/builtin/crush-config/SKILL.md`

### Skill Discovery States

The skills system monitors discovery state for each skill:

```mermaid
stateDiagram-v2
    [*] --> Unknown
    Unknown --> Discovering: Start scan
    Discovering --> Discovered: Success
    Discovering --> Error: Parse error
    Error --> Discovering: Retry
    Discovered --> [*]
```

State values are defined as:

```go
const (
    StateUnknown SkillDiscoveryState = iota
    StateDiscovering
    StateDiscovered
    StateError
)
```

## Server Integration

### REST API Endpoints

The server exposes skill-related endpoints for client access:

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/workspaces/{id}/skills` | GET | List workspace skills |
| `/workspaces/{id}/skills/read` | POST | Read skill content |
| `/workspaces/{id}/skills` | POST | Create/update skill |

### Event Broadcasting

Skill events are broadcast via the server's event system:

```go
func skillsEventToProto(e skills.Event) proto.SkillsEvent {
    out := proto.SkillsEvent{States: make([]proto.SkillState, len(e.States))}
    for i, s := range e.States {
        entry := proto.SkillState{
            Name:  s.Name,
            Path:  s.Path,
            State: proto.SkillDiscoveryState(s.State),
        }
        if s.Err != nil {
            entry.Error = s.Err.Error()
        }
        out.States[i] = entry
    }
    return out
}
```

## Usage Patterns

### Automatic Skill Loading

Crush analyzes the current workspace context and automatically loads relevant skills:

1. **File type detection**: Identifies programming languages and frameworks
2. **Project structure analysis**: Detects framework patterns (e.g., React, Go, Python)
3. **Context matching**: Compares current context against skill instructions using embeddings

### Manual Invocation

Users can invoke skills through the commands palette:

```
> user:git-helpers
> project:api-docs
```

### Embedded Shell Expansion

Shell-style value expansion works within skill configurations:

```yaml
---
name: deployment-skill
description: Deployment helper
---
Use credentials from $DEPLOY_TOKEN or $(cat ~/.deploy-token)
```

Supported expansion syntax:
- `$VAR`: Direct variable substitution
- `${VAR:-default}`: Default value if variable is unset
- `$(command)`: Command substitution

## Event Flow

```mermaid
sequenceDiagram
    participant App as Application
    participant SM as Skills Manager
    participant Cat as Skill Catalog
    participant Bro as Event Broker
    participant UI as UI Layer
    
    App->>SM: Start()
    SM->>Cat: Scan paths
    Cat-->>SM: Discovered skills
    SM->>Bro: Publish Event
    Bro->>UI: Broadcast
    UI->>Cat: Subscribe
```

## Related Components

| Component | Relationship | Description |
|-----------|--------------|-------------|
| `pubsub.Broker` | Dependency | Publishes skill discovery events |
| `config.Config` | Configuration | Stores `skills_paths` and `disabled_skills` |
| `client.Client` | Consumer | Queries skill state via REST API |
| `app.Skills` | Owner | Manages skills lifecycle in application |

---

<a id='common-issues'></a>

## Common Issues and Troubleshooting

### Related Pages

Related topics: [Getting Started with Crush](#getting-started), [Configuration Guide](#configuration-guide), [LLM Provider Setup](#providers-setup)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [internal/db/connect.go](https://github.com/charmbracelet/crush/blob/main/internal/db/connect.go)
- [internal/db/db.go](https://github.com/charmbracelet/crush/blob/main/internal/db/db.go)
- [internal/lsp/manager.go](https://github.com/charmbracelet/crush/blob/main/internal/lsp/manager.go)
- [internal/agent/tools/bash.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/bash.go)
- [internal/event/all.go](https://github.com/charmbracelet/crush/blob/main/internal/event/all.go)
- [internal/log/log.go](https://github.com/charmbracelet/crush/blob/main/internal/log/log.go)
- [internal/cmd/logs.go](https://github.com/charmbracelet/crush/blob/main/internal/cmd/logs.go)
</details>

# Common Issues and Troubleshooting

This page documents known issues, common failure modes, and troubleshooting procedures for Crush. It covers database problems, LSP configuration issues, authentication failures, bash tool limitations, and workspace management problems. The content is derived from community-reported issues and verified against the codebase.

## Understanding Crash Error Flows

Crush uses a structured event system to propagate errors across the client-server architecture. When errors occur, they flow through multiple layers before reaching the user.

```mermaid
graph TD
    A[User Action] --> B[Client Request]
    B --> C[Server Controller]
    C --> D[Backend Business Logic]
    D --> E[Database/LSP/Tools]
    E -->|Error| F[Event Publication]
    F --> G[pubsub Broker]
    G --> H[Client Event Handler]
    H --> I[User Error Display]
    
    E -->|Success| J[Response]
    J --> I
```

The event system handles various event types including LSP state changes, MCP events, permission requests, and error notifications. Source: [internal/event/all.go](https://github.com/charmbracelet/crush/blob/main/internal/event/all.go)

## Database Issues

### Database Corruption

**Symptom:** Error message `ERROR failed to get session message` appearing frequently (every hour or two).

**Cause:** The SQLite database used for session storage becomes corrupted, typically during write operations or when the process is terminated unexpectedly.

**Affected Environments:** Virtual machines, containers, and systems with network-attached storage are particularly susceptible.

**Troubleshooting Steps:**

1. Check filesystem health:
   ```bash
   zpool status  # For ZFS systems
   fsck /path/to/device  # For ext4 and other filesystems
   ```

2. Verify Crush data directory permissions:
   ```bash
   ls -la ~/.local/share/crush/
   ```

3. Check for concurrent Crush instances accessing the same database.

**Solutions:**

| Solution | Command/Action |
|----------|---------------|
| Clear session history | Delete `~/.local/share/crush/crush.json` |
| Rebuild database | Restart Crush and allow automatic reconstruction |
| Check storage health | Run filesystem diagnostics |

Source: [internal/db/db.go](https://github.com/charmbracelet/crush/blob/main/internal/db/db.go)

### Database Connection Failures

**Symptom:** `ERROR failed to get session message` errors during normal operation.

The database connection is managed through `internal/db/connect.go`. Connection failures can occur when:

1. The database file is locked by another process
2. File permissions prevent read/write access
3. The storage device reports I/O errors

```go
// Database connection error handling pattern
if err != nil {
    return nil, fmt.Errorf("failed to connect: %w", err)
}
```

Source: [internal/db/connect.go](https://github.com/charmbracelet/crush/blob/main/internal/db/connect.go)

## LSP Configuration Issues

### Global LSP Config Not Working

**Symptom:** LSP configured in `~/.config/crush/crush.json` is not being picked up. The `deno check` command works from the terminal, but Crush cannot use the Deno LSP.

**Common Causes:**

| Cause | Verification |
|-------|--------------|
| Wrong config file location | Ensure config is in `~/.config/crush/crush.json` |
| Incorrect LSP definition | Check JSON schema compliance |
| Missing root markers | LSP needs valid markers to activate |

**Configuration Example:**
```json
{
  "lsp": {
    "deno": {
      "command": "deno",
      "args": ["lsp"],
      "root_markers": ["deno.lock", "deno.json"],
      "filetypes": ["js", "ts", "jsx", "tsx", "json"]
    }
  }
}
```

**Troubleshooting:**

1. Verify the Deno executable is in your PATH
2. Check that root markers exist in your project
3. Enable debug logging to see LSP initialization

The LSP manager handles server lifecycle, diagnostics, and state changes through a pubsub-based event system.

Source: [internal/lsp/manager.go](https://github.com/charmbracelet/crush/blob/main/internal/lsp/manager.go)

### LSP State Monitoring

LSP events are published through the event system with state information:

```go
type LSPEvent struct {
    Type            LSPEventType
    Name            string
    State           lsp.ServerState
    Error           error
    DiagnosticCount int
}
```

Source: [internal/app/lsp_events.go](https://github.com/charmbracelet/crush/blob/main/internal/app/lsp_events.go)

## Authentication and API Key Issues

### OpenRouter Authentication Failures

**Symptom:** `ERROR: Unauthorized` when using OpenRouter models despite having `OPENROUTER_API_KEY` set.

**Troubleshooting Checklist:**

| Step | Action |
|------|--------|
| 1 | Verify environment variable is set: `echo $OPENROUTER_API_KEY` |
| 2 | Check API key is valid and has not expired |
| 3 | Ensure the key has permissions for the specific model |
| 4 | Verify base URL configuration in `crush.json` |

**Configuration:**
```json
{
  "providers": {
    "openrouter": {
      "type": "openai-compat",
      "base_url": "https://openrouter.ai/api/v1",
      "api_key": "$OPENROUTER_API_KEY"
    }
  }
}
```

### DeepSeek API Errors (v0.68.0 Regression)

**Issue:** In version 0.68.0, all DeepSeek API requests returned HTTP 400 (Bad Request) errors.

**Status:** Fixed in v0.69.0.

If you encounter this issue, upgrade to v0.69.0 or later.

### OAuth Token Refresh Failures

**Symptom:** Authentication fails after initial setup, particularly with Hyper or Copilot providers.

**Cause:** OAuth tokens expire and need refresh. Multiple Crush instances running simultaneously could cause token refresh race conditions.

**Status:** Fixed in v0.65.3 for multi-session scenarios.

Source: [internal/client/config.go](https://github.com/charmbracelet/crush/blob/main/internal/client/config.go)

## Bash Tool Issues

### Interactive Command Hanging

**Symptom:** The bash tool hangs when running interactive commands like `git rebase -i`.

**Cause:** The bash tool did not properly handle interactive terminal mode, causing the process to block indefinitely.

**Status:** Fixed in v0.74.1. The bash tool now properly handles interactive commands.

Source: [internal/agent/tools/bash.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/bash.go)

### Security Restrictions on Bash Commands

**Symptom:** Commands like `curl`, `wget`, `ssh`, `sudo` are blocked or fail to execute.

**Context:** This is by design for security reasons. The bash tool has built-in restrictions to prevent potentially harmful operations.

**Workaround:** For development and testing workflows requiring unrestricted access, consider:

1. Using the shell tool directly in a terminal session
2. Breaking complex operations into smaller, approved commands
3. Creating wrapper scripts for frequently used restricted commands

Source: [internal/agent/tools/bash.go](https://github.com/charmbracelet/crush/blob/main/internal/agent/tools/bash.go)

## Workspace and Session Issues

### Workspace Resolution Problems

**Symptom:** Crush cannot find or access the correct workspace directory.

**Troubleshooting:**

```go
// Workspace resolution handles symlinks and non-existent paths
func resolveWorkspaceKey(key string) (string, error) {
    // Symlinks are evaluated
    real, err := filepath.EvalSymlinks(tmp)
    // Non-existent paths fall back to absolute path
}
```

Source: [internal/backend/backend_test.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/backend_test.go)

### Session Message Retrieval Failures

**Symptom:** Cannot retrieve session history, prompts queue incorrectly.

**Common Causes:**

| Cause | Error Message | Solution |
|-------|---------------|----------|
| Database corruption | `failed to get session message` | Clear database, restart |
| Workspace shutdown | Session not found | Create new session |
| Invalid session ID | `ErrInvalidSessionID` | Use valid session ID format |

### Client-Server Architecture Issues

**Context:** The experimental client-server architecture can be enabled with `CRUSH_CLIENT_SERVER=1`.

**Known Limitations:**
- Not recommended for daily use in experimental mode
- Multiple concurrent instances may cause token refresh conflicts
- Database locking can occur with shared storage

Source: [internal/server/proto.go](https://github.com/charmbracelet/crush/blob/main/internal/server/proto.go)

## Logging and Diagnostics

### Accessing Crush Logs

Crush maintains structured logs for troubleshooting. Access logs using:

```bash
crush logs
```

### Log Configuration

The logging system captures:
- Server startup and shutdown events
- Database operations
- LSP initialization and diagnostics
- MCP server events
- Permission requests
- Error conditions

```go
// Log entries include structured fields for debugging
log.Error("operation failed", 
    "component", "lsp",
    "error", err.Error(),
    "workspace", ws.ID)
```

Source: [internal/log/log.go](https://github.com/charmbracelet/crush/blob/main/internal/log/log.go)

Source: [internal/cmd/logs.go](https://github.com/charmbracelet/crush/blob/main/internal/cmd/logs.go)

## Permission System Issues

### Permission Profiles (Issue #2935)

**Symptom:** Cannot switch between Yolo (auto-approve) and Safe (prompt) modes without restarting Crush.

**Current Behavior:** The `permissions.yolo` setting in `crush.json` is applied globally for the entire session. Runtime switching between permission modes is not supported.

**Workaround:** Modify `crush.json` and restart Crush to change permission behavior.

**Expected Future Behavior:** Support for multiple permission profiles (Yolo, Safe, Custom) switchable at runtime via command.

## Event System Reference

### Event Types and Payloads

| Event Type | Payload | Description |
|------------|---------|-------------|
| LSP State Changed | LSPEvent | LSP server state transitions |
| Diagnostics Changed | LSPEvent | File diagnostics updated |
| MCP Event | MCPEvent | MCP server notifications |
| Permission Request | PermissionRequest | Tool execution approval needed |
| Permission Notification | PermissionNotification | Permission state changes |
| Skills Event | SkillsEvent | Skill discovery state updates |

Source: [internal/server/events.go](https://github.com/charmbracelet/crush/blob/main/internal/server/events.go)

## Performance Considerations

### Long Sessions and Messages (v0.69.1)

Version 0.69.1 addressed performance issues with:
- Long sessions
- Long messages
- Extended thinking blocks
- Mouse selection
- Scrolling operations

If you experience performance degradation with extended use, consider upgrading to the latest version.

## Getting Help

If issues persist after trying the troubleshooting steps above:

1. Check the [GitHub Issues](https://github.com/charmbracelet/crush/issues) for similar reports
2. Enable debug logging with `CRUSH_DEBUG=1`
3. Collect logs using `crush logs`
4. Include your Crush version: `crush --version`
5. Report the issue with reproduction steps

---

<a id='sessions-and-history'></a>

## Sessions and History

### Related Pages

Related topics: [Common Issues and Troubleshooting](#common-issues), [Getting Started with Crush](#getting-started)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [internal/client/proto.go](https://github.com/charmbracelet/crush/blob/main/internal/client/proto.go)
- [internal/backend/session.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/session.go)
- [internal/server/events.go](https://github.com/charmbracelet/crush/blob/main/internal/server/events.go)
- [internal/server/proto.go](https://github.com/charmbracelet/crush/blob/main/internal/server/proto.go)
- [internal/backend/backend.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/backend.go)
- [internal/backend/events.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/events.go)
- [internal/backend/backend_test.go](https://github.com/charmbracelet/crush/blob/main/internal/backend/backend_test.go)
- [internal/app/testing.go](https://github.com/charmbracelet/crush/blob/main/internal/app/testing.go)
</details>

# Sessions and History

Crush maintains a comprehensive session and history system that tracks all interactions within a workspace. Sessions serve as the primary unit of organization for conversations, storing messages, history items, and metadata that enable context persistence across the agent's lifecycle.

## Architecture Overview

The session system follows a layered architecture with clear separation between the backend business logic, server transport layer, and client API.

```mermaid
graph TD
    Client["Client API<br/>internal/client/proto.go"] --> Server["HTTP Server<br/>internal/server/proto.go"]
    Server --> Backend["Backend<br/>internal/backend/session.go"]
    Backend --> Workspace["Workspace<br/>internal/workspace/workspace.go"]
    Workspace --> Sessions["Sessions Manager"]
    Workspace --> Messages["Messages Store"]
    Workspace --> History["History Store"]
    
    Sessions --> DB["SQLite Database<br/>internal/db/sessions.sql.go"]
    Messages --> DB
    History --> DB
```

### Key Components

| Component | File | Responsibility |
|-----------|------|----------------|
| Backend Session API | `internal/backend/session.go` | Business logic for session operations |
| HTTP Handlers | `internal/server/proto.go` | REST endpoint handlers |
| Client API | `internal/client/proto.go` | Client-side session methods |
| Database Layer | `internal/db/sessions.sql.go` | Persistent storage for sessions |

## Sessions

Sessions represent individual conversation contexts within a workspace. Each session maintains its own message history and state, enabling users to switch between different work contexts.

### Session Data Model

Sessions contain the following core attributes:

- **ID**: Unique identifier for the session
- **Title**: Human-readable session name
- **Created/Updated timestamps**: Session lifecycle tracking
- **Messages**: All messages exchanged within the session
- **History**: Chronological record of actions and events

### Session Lifecycle Operations

#### Creating Sessions

New sessions are created through the client API or directly via the backend:

```go
// Client-side: internal/client/proto.go
func (c *Client) CreateSession(ctx context.Context, id string, title string) (*proto.Session, error)
```

```go
// Backend: internal/backend/session.go
func (b *Backend) CreateSession(ctx context.Context, workspaceID string, title string) (session.Session, error)
```

The server validates the workspace exists and assigns a unique session ID before persisting to the database.

#### Listing Sessions

Retrieve all sessions within a workspace:

```go
// Client API
func (c *Client) ListSessions(ctx context.Context, id string) ([]proto.Session, error)

// Backend implementation
func (b *Backend) ListSessions(ctx context.Context, workspaceID string) ([]session.Session, error)
```

Source: [internal/backend/session.go:24-36]()

#### Getting a Specific Session

Retrieve a single session by ID:

```go
func (b *Backend) GetSession(ctx context.Context, workspaceID, sessionID string) (session.Session, error)
```

Source: [internal/backend/session.go:13-22]()

#### Saving/Updating Sessions

Session updates are persisted through the backend:

```go
// internal/backend/session.go
func (b *Backend) SaveSession(ctx context.Context, workspaceID string, sess session.Session) (session.Session, error)
```

Source: [internal/backend/session.go:47-54]()

#### Deleting Sessions

Sessions can be permanently removed:

```go
// internal/backend/session.go
func (b *Backend) DeleteSession(ctx context.Context, workspaceID, sessionID string) error
```

Source: [internal/backend/session.go:56-62]()

### HTTP API Endpoints

The server exposes session operations via REST endpoints:

| Method | Endpoint | Handler | Description |
|--------|----------|---------|-------------|
| GET | `/workspaces/{id}/sessions` | `handleGetWorkspaceSessions` | List all sessions |
| POST | `/workspaces/{id}/sessions` | `handlePostWorkspaceSessions` | Create new session |
| GET | `/workspaces/{id}/sessions/{sid}` | `handleGetWorkspaceSession` | Get specific session |
| DELETE | `/workspaces/{id}/sessions/{sid}` | `handleDeleteWorkspaceSession` | Delete session |

Source: [internal/server/proto.go]()

## Messages

Messages are the core content units within a session, representing individual exchanges between the user and the agent.

### Message Types

The system supports multiple message content types through discriminated unions:

| Type | Description |
|------|-------------|
| `TextContent` | Plain text or markdown content |
| `ImageContent` | Inline images with URL and detail |
| `BinaryContent` | Binary data with MIME type and path |

Source: [internal/server/events.go:89-100]()

### Message Retrieval Operations

#### List All Session Messages

Retrieve every message in a session:

```go
// Client API
func (c *Client) ListMessages(ctx context.Context, id string, sessionID string) ([]proto.Message, error)

// Backend
func (b *Backend) ListSessionMessages(ctx context.Context, workspaceID, sessionID string) ([]message.Message, error)
```

Source: [internal/backend/session.go:24-36]()

#### List User Messages Only

Filter to retrieve only user-role messages:

```go
// Client API
func (c *Client) ListUserMessages(ctx context.Context, id string, sessionID string) ([]proto.Message, error)

// Backend
func (b *Backend) ListUserMessages(ctx context.Context, workspaceID, sessionID string) ([]message.Message, error)
```

Source: [internal/backend/session.go:64-74]()

#### List All User Messages Across Sessions

Retrieve user messages from all sessions in a workspace:

```go
// Client API
func (c *Client) ListAllUserMessages(ctx context.Context, id string) ([]proto.Message, error)

// Backend
func (b *Backend) ListAllUserMessages(ctx context.Context, workspaceID string) ([]message.Message, error)
```

Source: [internal/backend/session.go:76-85]()

### HTTP API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/workspaces/{id}/sessions/{sid}/messages` | Get all messages for session |
| GET | `/workspaces/{id}/sessions/{sid}/messages/user` | Get user messages only |
| GET | `/workspaces/{id}/messages/user` | Get all user messages across sessions |

Source: [internal/server/proto.go]()

## History

The history system tracks a chronological record of actions and events within each session, providing an audit trail and enabling features like session resumption.

### History Items

History items record significant events during agent execution:

- Tool invocations and results
- File system changes
- Command executions
- State transitions

### Listing Session History

```go
// internal/backend/session.go
func (b *Backend) ListSessionHistory(ctx context.Context, workspaceID, sessionID string) (any, error) {
    ws, err := b.GetWorkspace(workspaceID)
    if err != nil {
        return nil, err
    }
    return ws.History.ListBySession(ctx, sessionID)
}
```

Source: [internal/backend/session.go:38-45]()

### Session History Files

The client can retrieve files associated with history items:

```go
// internal/client/proto.go
func (c *Client) ListSessionHistoryFiles(ctx context.Context, id string, sessionID string) ([]proto.File, error)
```

This endpoint returns files read or modified during the session, useful for understanding what resources were accessed.

Source: [internal/client/proto.go:24-37]()

### File Tracker Integration

Sessions track files accessed during execution through the file tracker system:

```go
// internal/server/proto.go
func (c *controllerV1) handleGetWorkspaceSessionFileTrackerFiles(w http.ResponseWriter, r *http.Request)
```

| Endpoint | Description |
|----------|-------------|
| `GET /workspaces/{id}/sessions/{sid}/filetracker/files` | List files read in a session |

Source: [internal/server/proto.go]()

## Event System

Sessions emit events for real-time updates, enabling the client to track session state changes and agent activity.

### Event Types

| Event Type | Payload | Description |
|------------|---------|-------------|
| `AgentEvent` | SessionID, SessionTitle, Type | Agent state changes |
| `RunComplete` | SessionID, RunID, MessageID, Text, Error, Cancelled | Run completion |
| `PermissionRequest` | ToolCallID, ToolName, Description, Action | Permission requests |
| `PermissionNotification` | ToolCallID, ToolName, State | Permission state changes |
| `LSPEvent` | Type, Name, State, Error, DiagnosticCount | LSP server events |
| `MCPEvent` | Type, Name, State, Error, ToolCount | MCP server events |
| `SkillsEvent` | States array | Skill discovery states |
| `ConfigChanged` | Config data | Configuration updates |

Source: [internal/server/events.go]()

### Event Conversion to Proto

The server converts internal events to protocol buffer format for transmission:

```go
// internal/server/events.go
func messageToProto(m message.Message) proto.Message {
    msg := proto.Message{
        ID:        m.ID,
        Role:      proto.Role(m.Role),
        CreatedAt: m.CreatedAt.UnixMilli(),
    }
    // Content conversion...
    return msg
}

func messagesToProto(msgs []message.Message) []proto.Message {
    out := make([]proto.Message, len(msgs))
    for i, m := range msgs {
        out[i] = messageToProto(m)
    }
    return out
}
```

Source: [internal/server/events.go:145-170]()

## Workspace Context

Sessions are always scoped to a workspace, providing filesystem context and configuration.

### Workspace-Session Relationship

```mermaid
graph LR
    Workspace["Workspace<br/>internal/workspace/workspace.go"] --> Sessions["Sessions Store"]
    Workspace --> Messages["Messages Store"]
    Workspace --> History["History Store"]
    Workspace --> Config["Config Store"]
    
    Sessions --> Session1["Session 1"]
    Sessions --> Session2["Session 2"]
    Sessions --> SessionN["Session N"]
```

### Workspace Operations

| Method | Description |
|--------|-------------|
| `GetWorkspace(id)` | Retrieve workspace by ID |
| `GetWorkspaceProto(id)` | Get workspace as proto representation |
| `VersionInfo()` | Server version information |
| `Config()` | Server-level configuration |

Source: [internal/backend/backend.go:57-100]()

## Backend Core Operations

The backend manages session lifecycle through several key operations:

```go
// Workspace key resolution
func resolveWorkspaceKey(path string) (string, error) {
    abs, err := filepath.Abs(path)
    if err != nil {
        return "", err
    }
    if resolved, err := filepath.EvalSymlinks(abs); err == nil {
        return resolved, nil
    }
    return abs, nil
}
```

Source: [internal/backend/backend.go:157-168]()

### Client Registration and Hold Mechanism

Sessions maintain a hold system to prevent premature cleanup:

```go
// Test verification: internal/backend/backend_test.go
func TestReleaseHold_NoStreams(t *testing.T) {
    b, _ := newTestBackend(t)
    ws, shutdowns := insertTestWorkspace(t, b, "/tmp/a")
    
    cid := newClientID(t)
    b.registerClient(ws, cid)
    require.NoError(t, b.releaseHold(ws.ID, cid))
    
    require.Equal(t, int32(1), shutdowns.Load())
}
```

Source: [internal/backend/backend_test.go:95-106]()

## Session Summarization

When context windows approach capacity, sessions can be summarized to preserve essential information:

```go
// Client API
func (c *Client) SummarizeSession(ctx context.Context, id, sessionID string) error

// Server handler
func (c *controllerV1) handlePostWorkspaceSessionSummarize(...)
```

Summarization preserves:
- Key decisions and outcomes
- Important file modifications
- Task completion status

This feature directly addresses context window management concerns mentioned in v0.66.0 release notes about summarization fixes.

## Error Handling

The backend defines specific errors for session operations:

| Error | Description |
|-------|-------------|
| `ErrWorkspaceNotFound` | Requested workspace does not exist |
| `ErrClientNotAttached` | Client not attached to workspace |
| `ErrInvalidClientID` | Client ID validation failed |

Source: [internal/backend/backend.go:32-41]()

## Testing

The backend includes comprehensive test coverage for session management:

```go
// internal/backend/backend_test.go
func TestHoldExpiry_TearsDown(t *testing.T) {
    b, srvShutdowns := newTestBackend(t)
    ws, wsShutdowns := insertTestWorkspace(t, b, "/tmp/a")
    
    cid := newClientID(t)
    b.registerClient(ws, cid)
    
    require.Eventually(t, func() bool {
        return wsShutdowns.Load() == 1 && srvShutdowns.Load() == 1
    }, 1*time.Second, 5*time.Millisecond)
}
```

Source: [internal/backend/backend_test.go:83-93]()

### Test Utilities

The app package provides test-specific shutdown handling:

```go
// internal/app/testing.go
func (app *App) ShutdownForTest() {
    for _, cleanup := range app.cleanupFuncs {
        if cleanup != nil {
            _ = cleanup(context.Background())
        }
    }
    app.cleanupFuncs = nil
}
```

Source: [internal/app/testing.go]()

## Related Documentation

- **Workspaces**: Sessions exist within workspace context; see workspace documentation for lifecycle management
- **Messages**: Detailed message content types and formatting
- **Events**: Real-time event subscription and handling
- **Configuration**: Session-specific configuration options

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Pitfall Log

Project: charmbracelet/crush

Summary: Found 27 structured pitfall item(s), including 0 high/blocking item(s). Top priority: Installation risk - Installation risk requires verification.

## 1. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this installation risk before relying on the project: Status line obscured by onboarding dialog
- User impact: Developers may fail before the first successful local run: Status line obscured by onboarding dialog
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: Status line obscured by onboarding dialog. Context: Observed when using macos
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_issue | fmev_a0f07b57c88e616e55f08e80117c394f | https://github.com/charmbracelet/crush/issues/3044

## 2. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Suggested check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: community_evidence:github | cevd_e98d3521bdb84c1398a9b2bbf776e5ae | https://github.com/charmbracelet/crush/issues/3044

## 3. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this configuration risk before relying on the project: Qwen 3.7 Max on OpenCode Go Unauthorized Error
- User impact: Developers may misconfigure credentials, environment, or host setup: Qwen 3.7 Max on OpenCode Go Unauthorized Error
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: Qwen 3.7 Max on OpenCode Go Unauthorized Error. Context: Observed when using linux
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_issue | fmev_9e6004420beb68218b1e13d4bcf43db6 | https://github.com/charmbracelet/crush/issues/3027

## 4. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this configuration risk before relying on the project: v0.68.0
- User impact: Upgrade or migration may change expected behavior: v0.68.0
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: v0.68.0. Context: Source discussion did not expose a precise runtime context.
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_release | fmev_bbaee295de66f70f36b7dc8593a9b7bd | https://github.com/charmbracelet/crush/releases/tag/v0.68.0

## 5. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this configuration risk before relying on the project: v0.70.0
- User impact: Upgrade or migration may change expected behavior: v0.70.0
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: v0.70.0. Context: Source discussion did not expose a precise runtime context.
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_release | fmev_13d76ec440a4dbc5cec24de4d067e76e | https://github.com/charmbracelet/crush/releases/tag/v0.70.0

## 6. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this configuration risk before relying on the project: v0.71.0
- User impact: Upgrade or migration may change expected behavior: v0.71.0
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: v0.71.0. Context: Observed when using python
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_release | fmev_8c04dff267efacde979bed34b0d877cd | https://github.com/charmbracelet/crush/releases/tag/v0.71.0

## 7. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this configuration risk before relying on the project: v0.74.0
- User impact: Upgrade or migration may change expected behavior: v0.74.0
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: v0.74.0. Context: Observed when using windows
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_release | fmev_a06bc501b775f717bcff8195feac4cd4 | https://github.com/charmbracelet/crush/releases/tag/v0.74.0

## 8. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Suggested check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: community_evidence:github | cevd_fbc15acd4ea14549b9798314e4d6b7df | https://github.com/charmbracelet/crush/issues/3045

## 9. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Suggested check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: community_evidence:github | cevd_c8619fe17ed64827934419fb0b4880c7 | https://github.com/charmbracelet/crush/issues/3046

## 10. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Suggested check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: community_evidence:github | cevd_bc68f6499e6d498fa2fcd70a2c6bdf8f | https://github.com/charmbracelet/crush/issues/3024

## 11. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Suggested check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: capability.assumptions | github_repo:987670088 | https://github.com/charmbracelet/crush

## 12. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Suggested check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | github_repo:987670088 | https://github.com/charmbracelet/crush

## 13. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Suggested check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: downstream_validation.risk_items | github_repo:987670088 | https://github.com/charmbracelet/crush

## 14. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Suggested check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: risks.scoring_risks | github_repo:987670088 | https://github.com/charmbracelet/crush

## 15. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Suggested check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: community_evidence:github | cevd_446e93f99e5d4dc1adfcc57444e1ef8d | https://github.com/charmbracelet/crush/issues/3027

## 16. Capability evidence risk - Capability evidence risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this capability risk before relying on the project: Custom provider models are invisible in the TUI
- User impact: Developers may hit a documented source-backed failure mode: Custom provider models are invisible in the TUI
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: Custom provider models are invisible in the TUI. Context: Observed when using linux
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_issue | fmev_95736f381b4ee813c4baceb87c276b87 | https://github.com/charmbracelet/crush/issues/3045

## 17. Capability evidence risk - Capability evidence risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this capability risk before relying on the project: Disabled models still appear in the TUI model picker
- User impact: Developers may hit a documented source-backed failure mode: Disabled models still appear in the TUI model picker
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: Disabled models still appear in the TUI model picker. Context: Observed when using linux
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_issue | fmev_07c27ec8a35d211b3baf8f95bdc94049 | https://github.com/charmbracelet/crush/issues/3046

## 18. Capability evidence risk - Capability evidence risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this capability risk before relying on the project: I hope to enable support for glm, qwen, kimi and minimax in the eastopenrouter.
- User impact: Developers may hit a documented source-backed failure mode: I hope to enable support for glm, qwen, kimi and minimax in the eastopenrouter.
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: I hope to enable support for glm, qwen, kimi and minimax in the eastopenrouter.. Context: Source discussion did not expose a precise runtime context.
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_issue | fmev_63a8cad40e7a346a6d7d99b6a8c6c551 | https://github.com/charmbracelet/crush/issues/3043

## 19. Capability evidence risk - Capability evidence risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this capability risk before relying on the project: rate limited api requests (429) are not retried
- User impact: Developers may hit a documented source-backed failure mode: rate limited api requests (429) are not retried
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: rate limited api requests (429) are not retried. Context: Source discussion did not expose a precise runtime context.
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_issue | fmev_4fd1758958ecbb14a35cdcd3d5d1999a | https://github.com/charmbracelet/crush/issues/3024

## 20. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Suggested check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | github_repo:987670088 | https://github.com/charmbracelet/crush

## 21. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Suggested check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | github_repo:987670088 | https://github.com/charmbracelet/crush

## 22. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: nightly
- User impact: Upgrade or migration may change expected behavior: nightly
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: nightly. Context: Source discussion did not expose a precise runtime context.
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_release | fmev_5728cb735d1e881463679d2371cb1b72 | https://github.com/charmbracelet/crush/releases/tag/nightly

## 23. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v0.69.0
- User impact: Upgrade or migration may change expected behavior: v0.69.0
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: v0.69.0. Context: Source discussion did not expose a precise runtime context.
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_release | fmev_5566acb97cddbf499b1f66a5e1c3c551 | https://github.com/charmbracelet/crush/releases/tag/v0.69.0

## 24. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v0.69.1
- User impact: Upgrade or migration may change expected behavior: v0.69.1
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: v0.69.1. Context: Source discussion did not expose a precise runtime context.
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_release | fmev_9d60f677f778da5c768a8a4010d2b98f | https://github.com/charmbracelet/crush/releases/tag/v0.69.1

## 25. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v0.72.0
- User impact: Upgrade or migration may change expected behavior: v0.72.0
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: v0.72.0. Context: Observed when using windows
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_release | fmev_aaf5977fa237d63e149c54782afa2165 | https://github.com/charmbracelet/crush/releases/tag/v0.72.0

## 26. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v0.73.0
- User impact: Upgrade or migration may change expected behavior: v0.73.0
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: v0.73.0. Context: Source discussion did not expose a precise runtime context.
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_release | fmev_17c84212866ab12078588df528f11ade | https://github.com/charmbracelet/crush/releases/tag/v0.73.0

## 27. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v0.74.1
- User impact: Upgrade or migration may change expected behavior: v0.74.1
- Suggested check: Before packaging this project, run the relevant install/config/quickstart check for: v0.74.1. Context: Source discussion did not expose a precise runtime context.
- Guardrail: State this as source-backed community evidence, not as Doramagic reproduction.
- Evidence: failure_mode_cluster:github_release | fmev_b32360eb9d88abe0b1576258d57cfdbb | https://github.com/charmbracelet/crush/releases/tag/v0.74.1

<!-- canonical_name: charmbracelet/crush; human_manual_source: deepwiki_human_wiki -->
