Doramagic Project Pack ยท Human Manual
crush
| Component | Purpose | Location | |-----------|---------|----------| | App | Main application state and coordination | internal/app/app.go | | Backend | Workspace and session management |...
Getting Started with Crush
Related topics: Configuration Guide, LLM Provider Setup
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Configuration Guide, LLM Provider Setup
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 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
# Homebrew
brew install charmbracelet/tap/crush
# MacPorts
sudo port install crush
Linux
# Binary installer
curl -fsSL https://charm.sh/crush/install.sh | sh
# via nix
nix run github:charmbracelet/crush
# FreeBSD
pkg install crush
Windows
# 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 in nur.repos.charmbracelet.crush. NixOS and Home Manager modules are also available:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nur.url = "github:nix-community/NUR";
};
}
Source: 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 directoryCRUSH_GLOBAL_DATA- Override data directory
Source: 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:
.crush.json(project-local)crush.json(project root)$HOME/.config/crush/crush.json(global)
Basic Configuration Structure
{
"$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:
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:
{
"$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
// 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
{
"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
Agent Skills
Crush supports the Agent Skills 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
{
"$schema": "https://charm.land/crush.json",
"options": {
"skills_paths": [
"~/.config/crush/skills",
"./project-skills"
]
}
}
Source: 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:
{
"$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:
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:
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
// 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
# 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
{
"lsp": {
"go": {
"command": "gopls",
"env": {
"GOTOOLCHAIN": "auto"
}
}
}
}
Workflow: Using MCP Servers
{
"mcp": {
"servers": {
"filesystem": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]
}
}
}
}
Architecture Overview
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 --> KCore 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:
- Filesystem integrity with
fsck - Disk health with SMART tools
- Ensure you're not running multiple instances simultaneously that share the same data directory
LSP Configuration Not Working
For global LSP config issues:
- Verify the config path:
~/.config/crush/crush.json - Check the LSP command is in your PATH
- Test the LSP directly:
gopls version(for Go)
OpenRouter/API Authentication Errors
# 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:
- Update to the latest version
- Avoid running truly interactive commands through the bash tool
- Use dedicated tools (git tool) for git operations
Source: Community 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.mdfiles to your skills directories - Enable client-server mode with
CRUSH_CLIENT_SERVER=1for remote access
For more advanced configuration, ask Crush to configure itself using the built-in crush-config skill.
Source: https://github.com/charmbracelet/crush / Human Manual
Project Structure
Related topics: Getting Started with Crush
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Getting Started with Crush
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
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 --> LLMDirectory 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:
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:
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:
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:
type clientState struct {
ID string
WorkspaceID string
LastSeen time.Time
// ...
}
Backend Lifecycle
Backend shutdown follows a graceful pattern:
s.backend.Shutdown()
The server shutdown callback triggers a coordinated shutdown sequence:
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:
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):
.crush.json- Project-local configurationcrush.json- Repository-local configuration$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
{
"$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:
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:
// @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:
// @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:
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:
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:
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:
{
"lsp": {
"go": {
"command": "gopls",
"env": { "GOTOOLCHAIN": "go1.24.5" }
}
}
}
Model Context Protocol (MCP)
MCP servers extend agent capabilities through stdio, HTTP, or SSE transports:
{
"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 | Feature request for multiple permission profiles (Yolo/Safe/Custom) switchable at runtime |
| #928 | Requests to remove bash command security limits for unrestricted terminal access |
| #2898 | Global LSP config not working - relates to configuration precedence |
| #2903 | Database corruption issues in certain environments |
| #2928 | OpenRouter model authentication failures |
Summary
Crush's architecture separates concerns into:
- Client Layer - CLI/TUI interfaces that communicate with the server
- Server Layer - HTTP API handling, routing, and event distribution
- Backend Core - Workspace management, session handling, and tool orchestration
- Data Layer - Database persistence and configuration management
- 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
Source: https://github.com/charmbracelet/crush / Human Manual
Configuration Guide
Related topics: Getting Started with Crush, LLM Provider Setup, Permission System
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Getting Started with Crush, LLM Provider Setup, Permission System
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):
.crush.json(project-local)crush.json(project root)$HOME/.config/crush/crush.json(user global)
Source: README.md:1
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
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
Configuration File Structure
The configuration file is a JSON object with the following schema:
{
"$schema": "https://charm.land/crush.json",
"lsp": { ... },
"mcp": { ... },
"providers": { ... },
"permissions": { ... },
"hooks": { ... },
"options": { ... },
"commit": { ... }
}
Source: README.md:1
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
{
"$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
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:
// 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
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
{
"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:
{
"command": "$MCP_SERVER",
"url": "$(cat /path/to/secret/token)"
}
Source: README.md:1
MCP Client API
The client provides methods for interacting with MCP servers:
// 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
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
Custom Providers
#### OpenAI-Compatible APIs
For non-OpenAI providers with OpenAI-compatible APIs, use openai-compat:
{
"$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
Provider API Management
The client supports dynamic provider API key management:
// 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
OAuth Providers
Crush supports OAuth-based authentication for providers like GitHub Copilot:
// 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
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
Yolo Mode
When permissions.yolo is set to true, all tools are auto-approved without prompting. This is suitable for trusted environments:
{
"permissions": {
"yolo": true
}
}
Permission Events
The system publishes permission events for auditing:
// 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
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
{
"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
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 (v0.67.0 release notes)
Commit Configuration
Crush can assist with git commits with configurable attribution styles:
{
"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 <[email protected]> |
none | No attribution trailer |
Source: README.md:1
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/skillsor~/.config/agents/skills/$XDG_CONFIG_HOME/crush/skillsor~/.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:
{
"$schema": "https://charm.land/crush.json",
"options": {
"skills_paths": [
"~/.config/crush/skills",
"./project-skills"
]
}
}
Skills Events
Skills discovery and loading are exposed via events:
// 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
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
{
"$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.
// 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
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:
// 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 Source: internal/backend/backend.go:1
Dynamic Configuration Updates
Configuration can be modified at runtime through the server API:
// 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
Configuration Architecture Diagram
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
OpenRouter API Key Not Working
When using OpenRouter, ensure the API key is properly exported:
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
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
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:
{
"options": {
"disabled_skills": ["crush-config"]
}
}
Source: README.md:1
Testing Configuration
For testing purposes, the application provides a ShutdownForTest method that runs cleanup functions without triggering full production shutdown:
func (app *App) ShutdownForTest() {
for _, cleanup := range app.cleanupFuncs {
if cleanup != nil {
_ = cleanup(context.Background())
}
}
app.cleanupFuncs = nil
}
Source: internal/app/testing.go:1
Source: https://github.com/charmbracelet/crush / Human Manual
LLM Provider Setup
Related topics: Getting Started with Crush, Configuration Guide
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Getting Started with Crush, Configuration Guide
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
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
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:
.crush.json(project-local)crush.json(project root)$HOME/.config/crush/crush.json(global)
Basic Provider Configuration
{
"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
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:
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:
{
"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:
err := client.SetProviderAPIKey(ctx, workspaceID, config.ScopeGlobal, "openai", "api-key-string")
Source: internal/client/config.go:35-55
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:
{
"$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
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:
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
Model Selection and Lookup
Finding Models Across Providers
The findModels function searches for models across configured providers:
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
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
OAuth-Based Providers
Some providers use OAuth authentication instead of API keys.
Hyper (Device Flow)
Hyper uses OAuth with device flow for authentication:
func (c *OAuthClient) Exchange(ctx context.Context, deviceCode,็ป็ป็ป็ป) (*Token, error)
Source: internal/oauth/hyper/device.go
GitHub Copilot (Token Import)
Copilot integration supports importing existing tokens:
func (c *Client) ImportCopilot(ctx context.Context, id string) (*oauth.Token, bool, error)
Source: internal/client/config.go:68-84
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
Provider Configuration Flow
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 asOPENROUTER_API_KEY, but I'm still getting errorsERROR: Unauthorized"
Ensure that:
- The
OPENROUTER_API_KEYenvironment variable is properly set - The API key has sufficient permissions
- The provider configuration uses
openai-compattype
Source: GitHub Issue #2928
Provider Not Found
If a model is not found, verify:
- The provider is properly configured in
crush.json - The provider name in the model string matches the configuration
- 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
See Also
- Configuration Reference - Full configuration documentation
- LSP Setup - Language Server Protocol integration
- Permissions System - Tool permission management
- Community Providers - Additional Crush-compatible models
Source: https://github.com/charmbracelet/crush / Human Manual
Built-in Tools
Related topics: Permission System, MCP Integration
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Permission System, MCP Integration
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.
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] -.-> D1Tool 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.
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 InstanceFile 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_stringspecification - 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
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.
Tool Execution Flow
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 ResultError 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:
{
"$schema": "https://charm.land/crush.json",
"tools": {
"bash": {
"timeout": 60
}
}
}
See Also
Source: https://github.com/charmbracelet/crush / Human Manual
Permission System
Related topics: Built-in Tools, Configuration Guide
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Built-in Tools, Configuration Guide
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:
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 --> LComponents
| 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.
{
"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
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:
{
"Skip": true
}
Source: internal/server/proto.go:77-91
Grant Permission
Grants or denies a pending permission request.
POST /workspaces/{id}/permissions/grant
Request Body:
{
"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
Backend Implementation
Workspace Permission Methods
The backend provides methods for managing workspace-level permission settings:
// 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
Permission Request Flow
When a tool execution requires permission:
- The agent initiates a permission request through
App.Permissions.Request() - The request is published to all connected SSE subscribers
- Any client can respond with a grant or denial
- The first grant resolves the pending request
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
Client SDK
The client provides methods for interacting with the permission system:
SetPermissionsSkipRequests
Enables or disables permission prompts for a workspace.
func (c *Client) SetPermissionsSkipRequests(ctx context.Context, id string, skip bool) error
Source: internal/client/proto.go:180-190
GetPermissionsSkipRequests
Retrieves the current permission skip setting.
func (c *Client) GetPermissionsSkipRequests(ctx context.Context, id string) (bool, error)
Source: internal/client/proto.go:192-208
Event System
Permission events are broadcast to all connected clients via SSE:
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 EventEvent 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
Error Handling
The API handles errors by mapping them to appropriate HTTP status codes:
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
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
Configuration
Permissions can be configured at three levels with the following priority:
.crush.json(project level)crush.json(project level)$HOME/.config/crush/crush.json(global)
Example configuration:
{
"$schema": "https://charm.land/crush.json",
"permissions": {
"yolo": false
}
}
Source: README.md
Community Considerations
Feature Request: Multiple Permission Profiles
Issue #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 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
// 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: https://github.com/charmbracelet/crush / Human Manual
MCP Integration
Related topics: Built-in Tools, Skills System
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Built-in Tools, Skills System
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.
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
MCP Client States
Each MCP client maintains a state machine that tracks its connection lifecycle. The integration supports the following states:
stateDiagram-v2
[*] --> Disconnected
Disconnected --> Connecting
Connecting --> Connected
Connected --> Disconnecting
Disconnecting --> Disconnected
Connecting --> Error
Connected --> Error
Error --> ConnectingMCPClientInfo 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
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
The event conversion function maps internal MCP event types to protocol buffer equivalents:
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
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
Get MCP Client States
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
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
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 OKSource: internal/backend/config.go:80-86
Backend Tool Refresh Implementation
// 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
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
Get Prompt Implementation
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
Prompt Command Building
Prompts are exposed as commands through the command system, enabling the AI agent to invoke them:
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
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
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
Read Resource Implementation
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
Server-side Resource Reading
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
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
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
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
Refresh Prompts Implementation
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
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
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
MCP Event Subscription Registration
setupSubscriber(ctx, app.serviceEventsWG, "mcp", mcp.SubscribeEvents, app.events)
Source: internal/app/app.go:44
Backend Event Handling
The backend provides workspace-specific event subscriptions for MCP operations.
SubscribeEvents Implementation
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
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
Example error handling pattern:
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
Data Flow Diagram
The following diagram illustrates the complete data flow for a typical MCP operation:
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 resultTesting Considerations
When testing MCP integration components, the test infrastructure provides utilities for creating mock workspaces with client state tracking:
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: https://github.com/charmbracelet/crush / Human Manual
Skills System
Related topics: MCP Integration, Configuration Guide
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: MCP Integration, Configuration Guide
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 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:
- Automatic Discovery: Crush analyzes the current context and automatically activates relevant skills based on file types, project structure, or detected workflow patterns.
- Manual Invocation: Users can explicitly invoke skills through the commands palette using
user:skill-namefor global skills orproject:skill-namefor project-local skills.
Architecture
Core Components
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:
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:
type Event struct {
Type EventType
States []SkillState
}
Event types include:
skills.EventUpdated: Published when skill states changeskills.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:
Source: https://github.com/charmbracelet/crush / Human Manual
Common Issues and Troubleshooting
Related topics: Getting Started with Crush, Configuration Guide, LLM Provider Setup
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Getting Started with Crush, Configuration Guide, LLM Provider Setup
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.
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 --> IThe event system handles various event types including LSP state changes, MCP events, permission requests, and error notifications. Source: 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:
``bash zpool status # For ZFS systems fsck /path/to/device # For ext4 and other filesystems ``
- Check filesystem health:
``bash ls -la ~/.local/share/crush/ ``
- Verify Crush data directory permissions:
- 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
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:
- The database file is locked by another process
- File permissions prevent read/write access
- The storage device reports I/O errors
// Database connection error handling pattern
if err != nil {
return nil, fmt.Errorf("failed to connect: %w", err)
}
Source: 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:
{
"lsp": {
"deno": {
"command": "deno",
"args": ["lsp"],
"root_markers": ["deno.lock", "deno.json"],
"filetypes": ["js", "ts", "jsx", "tsx", "json"]
}
}
}
Troubleshooting:
- Verify the Deno executable is in your PATH
- Check that root markers exist in your project
- 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
LSP State Monitoring
LSP events are published through the event system with state information:
type LSPEvent struct {
Type LSPEventType
Name string
State lsp.ServerState
Error error
DiagnosticCount int
}
Source: 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:
{
"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
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
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:
- Using the shell tool directly in a terminal session
- Breaking complex operations into smaller, approved commands
- Creating wrapper scripts for frequently used restricted commands
Source: internal/agent/tools/bash.go
Workspace and Session Issues
Workspace Resolution Problems
Symptom: Crush cannot find or access the correct workspace directory.
Troubleshooting:
// 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
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
Logging and Diagnostics
Accessing Crush Logs
Crush maintains structured logs for troubleshooting. Access logs using:
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
// Log entries include structured fields for debugging
log.Error("operation failed",
"component", "lsp",
"error", err.Error(),
"workspace", ws.ID)
Source: internal/log/log.go
Source: 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
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:
- Check the GitHub Issues for similar reports
- Enable debug logging with
CRUSH_DEBUG=1 - Collect logs using
crush logs - Include your Crush version:
crush --version - Report the issue with reproduction steps
Source: https://github.com/charmbracelet/crush / Human Manual
Sessions and History
Related topics: Common Issues and Troubleshooting, Getting Started with Crush
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Common Issues and Troubleshooting, Getting Started with Crush
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.
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 --> DBKey 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:
// Client-side: internal/client/proto.go
func (c *Client) CreateSession(ctx context.Context, id string, title string) (*proto.Session, error)
// 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:
// 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:
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:
// 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:
// 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:
// 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:
// 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:
// 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
// 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:
// 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:
// 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:
// 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
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:
// 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:
// 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:
// 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:
// 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:
// 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
Source: https://github.com/charmbracelet/crush / Human Manual
Doramagic Pitfall Log
Source-linked risks stay visible on the manual page so the preview does not read like a recommendation.
Developers may fail before the first successful local run: Status line obscured by onboarding dialog
May increase setup, validation, or first-run risk for the user.
Developers may misconfigure credentials, environment, or host setup: Qwen 3.7 Max on OpenCode Go Unauthorized Error
Upgrade or migration may change expected behavior: v0.68.0
Doramagic Pitfall Log
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
- 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
- Recommended check: Before packaging this project, run the relevant install/config/quickstart check for: Status line obscured by onboarding dialog. Context: Observed when using macos
- Evidence: failure_mode_cluster:github_issue | fmev_a0f07b57c88e616e55f08e80117c394f | https://github.com/charmbracelet/crush/issues/3044
2. Installation risk: Installation risk requires verification
- Severity: medium
- 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.
- Recommended 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
- 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
- Recommended 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
- Evidence: failure_mode_cluster:github_issue | fmev_9e6004420beb68218b1e13d4bcf43db6 | https://github.com/charmbracelet/crush/issues/3027
4. Configuration risk: Configuration risk requires verification
- Severity: medium
- 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
- Recommended 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.
- 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
- 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
- Recommended 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.
- 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
- 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
- Recommended check: Before packaging this project, run the relevant install/config/quickstart check for: v0.71.0. Context: Observed when using python
- 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
- 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
- Recommended check: Before packaging this project, run the relevant install/config/quickstart check for: v0.74.0. Context: Observed when using windows
- 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
- 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.
- Recommended 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
- 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.
- Recommended 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
- 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.
- Recommended 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
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended 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
- 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.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | github_repo:987670088 | https://github.com/charmbracelet/crush
Source: Doramagic discovery, validation, and Project Pack records
Community Discussion Evidence
These external discussion links are review inputs, not standalone proof that the project is production-ready.
Count of project-level external discussion links exposed on this manual page.
Open the linked issues or discussions before treating the pack as ready for your environment.
Community Discussion Evidence
Doramagic exposes project-level community discussion separately from official documentation. Review these links before using crush with real data or production workflows.
- Disabled models still appear in the TUI model picker - github / github_issue
- Custom provider models are invisible in the TUI - github / github_issue
- Status line obscured by onboarding dialog - github / github_issue
- I hope to enable support for glm, qwen, kimi and minimax in the eastopen - github / github_issue
- Qwen 3.7 Max on OpenCode Go Unauthorized Error - github / github_issue
- rate limited api requests (429) are not retried - github / github_issue
- Configuration risk requires verification - GitHub / issue
- Configuration risk requires verification - GitHub / issue
- Configuration risk requires verification - GitHub / issue
- Configuration risk requires verification - GitHub / issue
- Capability evidence risk requires verification - GitHub / issue
Source: Project Pack community evidence and pitfall evidence