gastownhall/beads · error

request failed (attempt %d/%d): %w

Error message

request failed (attempt %d/%d): %w

What it means

The HTTP transport itself failed on this attempt (connection refused, DNS failure, TLS error, timeout, context cancellation). The error is stored as lastErr with the attempt counter and the loop continues, retrying up to MaxRetries times. If all attempts fail, the final error is surfaced wrapped in 'max retries exceeded' (error 1778).

Source

Thrown at internal/gitlab/client.go:133

		if body != nil {
			jsonBody, err := json.Marshal(body)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to marshal request body: %w", err)
			}
			reqBody = bytes.NewReader(jsonBody)
		}

		req, err := http.NewRequestWithContext(ctx, method, urlStr, reqBody)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to create request: %w", err)
		}

		req.Header.Set("PRIVATE-TOKEN", c.Token)
		req.Header.Set("Content-Type", "application/json")

		resp, err := c.HTTPClient.Do(req)
		if err != nil {
			lastErr = fmt.Errorf("request failed (attempt %d/%d): %w", attempt+1, MaxRetries+1, err)
			continue
		}

		// Limit response body to 50MB to prevent OOM from malformed responses.
		const maxResponseSize = 50 * 1024 * 1024
		respBody, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
		_ = resp.Body.Close() // Best effort: HTTP body close; connection may be reused regardless
		if err != nil {
			lastErr = fmt.Errorf("failed to read response (attempt %d/%d): %w", attempt+1, MaxRetries+1, err)
			continue
		}

		if resp.StatusCode >= 200 && resp.StatusCode < 300 {
			return respBody, resp.Header, nil
		}

		// Retry on rate-limiting and server errors with exponential backoff.
		retriable := resp.StatusCode == http.StatusTooManyRequests ||

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check basic connectivity: curl -v https://your-gitlab/api/v4/version with your token
  2. Verify DNS/proxy settings (HTTPS_PROXY env) and that the host resolves
  3. Increase the HTTP client timeout if timeouts are the cause; check context deadlines upstream
  4. If the final error says 'max retries exceeded', inspect the wrapped lastErr for the root transport cause

Example fix

// before
client := &http.Client{} // default, may time out on slow networks
// after
client := &http.Client{Timeout: 60 * time.Second}
Defensive patterns

Strategy: retry

Validate before calling

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

Type guard

var netErr net.Error
if errors.As(err, &netErr) { /* transient network failure */ }

Try / catch

var netErr net.Error
if err != nil && errors.As(err, &netErr) {
	if netErr.Timeout() { /* back off and retry at app level */ }
}

Prevention

When it happens

Trigger: c.HTTPClient.Do returning a network-level error: unreachable GitLab host, DNS resolution failure, TLS handshake failure, request timeout, or the caller's ctx being canceled mid-request.

Common situations: Corporate proxy blocking gitlab.example.com; VPN down; wrong host/port in config; server temporarily down during maintenance; TLS cert issues with self-hosted GitLab; client-side timeout too low.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/3ac686c6fc6f618a. Report an issue: GitHub.