charmbracelet/crush · error

could not create request: %w

Error message

could not create request: %w

What it means

FetchCredits wraps any error returned by http.NewRequestWithContext when constructing the GET request to <BaseURL>/v1/credits. In practice this only fails if the composed URL is malformed (unparseable) or the context is invalid, since the method and nil body are constants. It indicates the request never left the client.

Source

Thrown at internal/agent/hyper/provider.go:87

//
// It returns nil when the team has hypercredit display disabled, in which
// case Hyper reports the balance in dollars instead and there is no
// hypercredit figure to show.
func FetchCredits(ctx context.Context, apiKey string) (*int, error) {
	if hasBalance.Load() {
		hasBalance.Store(false)
		balance := int(lastKnownBalance.Load())
		return &balance, nil
	}

	req, err := http.NewRequestWithContext(
		ctx,
		http.MethodGet,
		BaseURL()+"/v1/credits",
		nil,
	)
	if err != nil {
		return nil, fmt.Errorf("could not create request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+apiKey)

	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to make request: %w", err)
	}
	defer resp.Body.Close() //nolint:errcheck

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
	}

	// Teams with hypercredit display disabled get a balance_usd field
	// instead of balance, and no balance is shown for them at all.
	var result struct {
		Balance *int `json:"balance"`

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Print and inspect the env value with `echo "${HYPER_URL}" | cat -A` and fix HYPER_URL to a clean absolute URL like https://my-proxy.example.com (no spaces/quotes).
  2. Validate the URL before calling FetchCredits: u, err := url.Parse(hyper.BaseURL()+"/v1/credits"); require err == nil and u.Scheme != "" && u.Host != "".
  3. Ensure the context passed in is live (not already canceled) and carries no invalid values.
  4. Unset HYPER_URL to fall back to the default https://hyper.charm.land and retry.

Example fix

// before
os.Setenv("HYPER_URL", "https://my proxy.example.com") // invalid: space in host
balance, err := hyper.FetchCredits(ctx, apiKey)
// after
base := strings.TrimRight(strings.TrimSpace(os.Getenv("HYPER_URL")), "/")
if u, err := url.Parse(base + "/v1/credits"); err != nil || u.Host == "" {
    base = "https://hyper.charm.land" // fall back to default
}
_ = base // use validated base URL / corrected HYPER_URL before calling FetchCredits
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(hyper.BaseURL() + "/v1/credits")
if err != nil || u.Scheme == "" || u.Host == "" {
    return fmt.Errorf("invalid HYPER_URL %q: %w", os.Getenv("HYPER_URL"), err)
}

Type guard

func validURL(raw string) bool {
    u, err := url.Parse(raw)
    return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != ""
}

Try / catch

balance, err := hyper.FetchCredits(ctx, apiKey)
if err != nil {
    if strings.Contains(err.Error(), "could not create request") {
        log.Fatalf("check HYPER_URL: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: HYPER_URL is set to a value that makes BaseURL()+"/v1/credits" an unparseable URL (e.g. contains control characters, spaces, or an invalid scheme like "foo bar://x"); or the passed context is already canceled/invalid in a way NewRequestWithContext rejects.

Common situations: Developers misconfigure the HYPER_URL environment variable (trailing garbage, quotes included, whitespace) when pointing Crush Hyper at a self-hosted proxy; the URL concatenation then produces an invalid url.URL.

Related errors


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