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

  1. Export the named variable (from the error message) before launching Crush.
  2. Correct the reference syntax or variable name in the MCP/LSP env entry.
  3. Add the variable to a shell profile, direnv .envrc, or CI secret store so it is always present.
  4. 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

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


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/1dabce879d29c02e. Report an issue: GitHub.