charmbracelet/crush · error

failed to connect to provider %s: %s

Error message

failed to connect to provider %s: %s

What it means

Returned when the Z.AI provider probe responds with HTTP 401 Unauthorized during API-key validation. For ZAI, a 401 is treated as a definitive key failure (other statuses are tolerated as inconclusive), so the response status text is surfaced to the user.

Source

Thrown at internal/config/config.go:1038

		return fmt.Errorf("failed to create request for provider %s: %w", c.ID, err)
	}
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	for k, v := range c.ExtraHeaders {
		req.Header.Set(k, v)
	}

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to create request for provider %s: %w", c.ID, err)
	}
	defer resp.Body.Close()

	switch providerID {
	case catwalk.InferenceProviderZAI:
		if resp.StatusCode == http.StatusUnauthorized {
			return fmt.Errorf("failed to connect to provider %s: %s", c.ID, resp.Status)
		}
	default:
		if resp.StatusCode != http.StatusOK {
			return fmt.Errorf("failed to connect to provider %s: %s", c.ID, resp.Status)
		}
	}
	return nil
}

// 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
	}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Generate a fresh API key from the Z.AI console and update it in crushrc/crush.json.
  2. Confirm the key matches the endpoint region you configured (api.z.ai vs open.bigmodel.cn).
  3. Verify the env variable holding the key is actually set and non-empty for the process.
  4. Test the key directly: curl -H "Authorization: Bearer $KEY" https://api.z.ai/... to see the same 401.

Example fix

// before (crushrc)
provider zai
  api_key "{{ env:OLD_REVOKED_KEY }}"
end
// after
export ZAI_API_KEY=<new key from Z.AI console>
provider zai
  api_key "{{ env:ZAI_API_KEY }}"
end
Defensive patterns

Strategy: validation

Validate before calling

if zaiKey == "" {
    return errors.New("ZAI_API_KEY is not set; obtain one from the Z.AI console before validating")
}
// optional live pre-check:
resp, err := http.Get("https://api.z.ai/v1/models") // expect non-401

Type guard

func isAuthFailure(statusCode int) bool { return statusCode == http.StatusUnauthorized || statusCode == http.StatusForbidden }

Try / catch

if err := validateProviderKey(ctx, zaiCfg); err != nil {
    if strings.Contains(err.Error(), "401") {
        openKeyConsole("https://z.ai") // guide user to rotate key
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Calling API-key validation for InferenceProviderZAI and receiving a 401 response — the configured Z.AI API key is missing, expired, revoked, or malformed.

Common situations: Rotated or deleted Z.AI key still referenced in config; key set for the wrong Z.AI endpoint (international vs China); Bearer header not being sent because the key resolved to an empty string.

Related errors


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