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

  1. Set/verify the environment variables referenced in the configured LSP command before starting crush.
  2. Replace the templated command with an absolute executable path (e.g. /usr/local/bin/gopls) in the LSP config.
  3. Check that the command value in the LSP config is non-empty and correctly spelled.
  4. 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

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


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