charmbracelet/crush · error
env %s: %w
Error message
env %s: %w
What it means
Wraps a resolver failure while expanding the string-slice form of env settings (used for MCP servers, LSPs, etc.) in internal/config/config.go. Each entry is resolved and then joined as KEY=value; if resolving any entry fails, the error names the offending key. Like error 500, it signals an unset variable or bad env-reference syntax.
Source
Thrown at internal/config/config.go:1066
// resolveEnvs expands every value in envs through the given resolver
// and returns a fresh "KEY=value" slice sorted by key. The input map is
// not mutated. On the first resolution failure it returns nil and an
// error identifying the offending variable; the inner resolver error is
// already sanitized by ResolveValue and is wrapped with %w.
func resolveEnvs(envs map[string]string, r VariableResolver) ([]string, error) {
if len(envs) == 0 {
return nil, nil
}
keys := make([]string, 0, len(envs))
for k := range envs {
keys = append(keys, k)
}
slices.Sort(keys)
res := make([]string, 0, len(envs))
for _, k := range keys {
v, err := r.ResolveValue(envs[k])
if err != nil {
return nil, fmt.Errorf("env %s: %w", k, err)
}
res = append(res, fmt.Sprintf("%s=%s", k, v))
}
return res, nil
}
func ptrValOr[T any](t *T, el T) T {
if t == nil {
return el
}
return *t
}
View on GitHub (pinned to 7944b8e522)
Solutions
- Export the named variable (from the error message) before launching Crush.
- Correct the reference syntax or variable name in the MCP/LSP env entry.
- Add the variable to a shell profile, direnv .envrc, or CI secret store so it is always present.
- Remove the env entry if the tool does not actually require it.
Example fix
// before (MCP config)
mcp github
env GITHUB_TOKEN "{{ env:GITHUB_TKN }}" // typo
end
// after
export GITHUB_TOKEN=ghp_...
mcp github
env GITHUB_TOKEN "{{ env:GITHUB_TOKEN }}"
end Defensive patterns
Strategy: validation
Validate before calling
for _, entry := range mcpCfg.Env {
k, v, _ := strings.Cut(entry, "=")
if err := resolver.ResolveValue(v); err != nil {
return fmt.Errorf("MCP %q requires env %s which is unresolvable", mcpCfg.Name, k)
}
} Type guard
func allEnvKeysSet(envs []string, lookup func(string) bool) bool {
for _, e := range envs {
if k, v, ok := strings.Cut(e, "="); ok && strings.Contains(v, "env:") && !lookup(k) {
return false
}
}
return true
} Try / catch
if err := cfg.ResolveMCPEnvs(); err != nil {
if strings.Contains(err.Error(), "env ") {
log.Fatalf("export the missing variable listed in the error, then restart: %v", err)
}
return err
} Prevention
- Keep MCP/LSP env vars in direnv or a startup script so every shell has them.
- Maintain a .env.example listing all referenced variable names.
- Grep crushrc for {{ env: references and assert each exists in CI.
When it happens
Trigger: Config resolution of an MCP/LSP entry whose env list contains a value referencing an environment variable that cannot be resolved — unset variable or malformed {{ env:... }} reference.
Common situations: An MCP server config referencing a token env var that is only set in a different shell; LSP env entries copied from docs with placeholder variable names; missing .env file that a team relies on.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- invalid mcp command: %w
- mcp stdio config requires a non-empty 'command' field
- mcp http config requires a non-empty 'url' field
- oauth_client_id: %w
- mcp sse config requires a non-empty 'url' field
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/1dabce879d29c02e.
Report an issue: GitHub.