charmbracelet/crush · error
invalid lsp command: %w
Error message
invalid lsp command: %w
What it means
createPowernapClient calls resolver.ResolveValue on c.config.Command before spawning the LSP server process. When the command string contains shell/template expansions (e.g. environment variables, ~, config placeholders) that cannot be resolved, it returns this wrapped error. It indicates the LSP server command is not a concrete executable path/word after resolution, so no client can be created.
Source
Thrown at internal/lsp/client.go:193
done <- c.client.Exit()
}()
select {
case err := <-done:
return err
case <-closeCtx.Done():
c.client.Kill()
return closeCtx.Err()
}
}
// createPowernapClient creates a new powernap client with the current configuration.
func (c *Client) createPowernapClient() error {
rootURI := string(protocol.URIFromPath(c.cwd))
command, err := c.resolver.ResolveValue(c.config.Command)
if err != nil {
return fmt.Errorf("invalid lsp command: %w", err)
}
args, err := c.config.ResolvedArgs(c.resolver)
if err != nil {
return fmt.Errorf("invalid lsp args: %w", err)
}
envs, err := c.config.ResolvedEnv(c.resolver)
if err != nil {
return fmt.Errorf("invalid lsp env: %w", err)
}
clientConfig := powernap.ClientConfig{
Command: home.Long(command),
Args: args,
RootURI: rootURI,
Environment: envs,
Settings: c.config.Options,View on GitHub (pinned to 7944b8e522)
Solutions
- Set/verify the environment variables referenced in the configured LSP command before starting crush.
- Replace the templated command with an absolute executable path (e.g. /usr/local/bin/gopls) in the LSP config.
- Check that the command value in the LSP config is non-empty and correctly spelled.
- Run the command string manually in a shell to confirm it expands to a valid executable.
Example fix
// before
command = "${MY_GOPLS}/gopls" // MY_GOPLS not set
// after
command = "/usr/local/bin/gopls" // or export MY_GOPLS first Defensive patterns
Strategy: validation
Validate before calling
cmd := os.ExpandEnv(lspCfg.Command)
if strings.TrimSpace(cmd) == "" {
return fmt.Errorf("lsp command resolves to empty string: %q", lspCfg.Command)
}
if _, err := exec.LookPath(cmd); err != nil {
return fmt.Errorf("lsp command %q not found on PATH", cmd)
} Type guard
func validLSPCommand(cfg LSPConfig) bool {
return cfg.Command != "" && !strings.Contains(cfg.Command, "${")
} Try / catch
client, err := lsp.New(cfg)
if err != nil {
if strings.Contains(err.Error(), "invalid lsp command") {
slog.Error("Check the lsp command and its env expansions", "command", cfg.Command, "error", err)
}
return err
} Prevention
- Use absolute paths for LSP server commands in shared configs.
- Document required env vars next to the LSP config and set them in CI/containers.
- Smoke-test config resolution with a startup check that runs exec.LookPath early.
When it happens
Trigger: Calling New or Restart on an lsp.Client whose config.Command contains an unresolvable expansion — e.g. an env var that is unset at expansion time, or a resolver that fails on the configured command string.
Common situations: crush config defines an LSP command like "${GoplsPath}" or "$HOME/bin/gopls" but the variable is not exported in the environment where crush runs; command typo'd or left empty; command defined in a config file that assumes a different shell environment.
Related errors
- env %s: %w
- invalid lsp args: %w
- invalid lsp env: %w
- task agent not configured
- small model provider not configured
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/1d9b7eb6a3d9dd6c.
Report an issue: GitHub.