charmbracelet/crush · error

failed to make request: %w

Error message

failed to make request: %w

What it means

FetchCredits wraps errors from http.Client.Do when executing the GET <BaseURL>/v1/credits request. This covers DNS failures, connection refused/reset, TLS errors, and the client's 10-second timeout. The HTTP call never completed, so no status code exists.

Source

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

		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"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	return result.Balance, nil
}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check basic reachability: `curl -v $HYPER_URL/v1/credits` (or https://hyper.charm.land/v1/credits) to see the underlying network error.
  2. If self-hosting via HYPER_URL, verify the proxy is up and listening on the expected port.
  3. Check proxy environment (HTTP_PROXY/HTTPS_PROXY) and VPN/firewall rules that may block the host.
  4. For timeouts, retry the call; FetchCredits has no built-in retry and the client timeout is fixed at 10 seconds.
  5. Respect ctx cancellation: if the caller canceled, fix the caller's timeout/deadline instead of retrying.

Example fix

// before
balance, err := hyper.FetchCredits(ctx, apiKey)
if err != nil { return err } // fails on transient network blips
// after
var balance *int
var err error
for attempt := 0; attempt < 3; attempt++ {
    balance, err = hyper.FetchCredits(ctx, apiKey)
    if err == nil || ctx.Err() != nil || !errors.Is(err, context.DeadlineExceeded) && !isNetError(err) {
        break
    }
    time.Sleep(time.Duration(attempt+1) * 500 * time.Second / 1000)
}
Defensive patterns

Strategy: retry

Validate before calling

conn, err := net.DialTimeout("tcp", host(hyper.BaseURL())+":443", 3*time.Second)
if err != nil {
    return fmt.Errorf("hyper endpoint unreachable: %w", err)
}
conn.Close()

Type guard

func isNetworkError(err error) bool {
    var ne net.Error
    if errors.As(err, &ne) { return true }
    var oe *net.OpError
    return errors.As(err, &oe) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, syscall.ECONNREFUSED)
}

Try / catch

var balance *int
err := retryDo(3, 500*time.Millisecond, func() error {
    var e error
    balance, e = hyper.FetchCredits(ctx, apiKey)
    if e != nil && isNetworkError(e) {
        return e // retryable
    }
    return stop(e) // fatal
})

Prevention

When it happens

Trigger: Network is down or DNS cannot resolve hyper.charm.land (or the HYPER_URL host); the server is unreachable/firewalled; TLS handshake fails (bad proxy, clock skew); the 10s client Timeout elapses; or ctx is canceled mid-request.

Common situations: Corporate proxy/VPN blocks hyper.charm.land; offline development; self-hosted HYPER_URL proxy not running (connection refused); slow networks tripping the 10-second timeout.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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