{"record":{"id":"3ac686c6fc6f618a","repo":"gastownhall/beads","slug":"request-failed-attempt-d-d-w","errorCode":null,"errorMessage":"request failed (attempt %d/%d): %w","messagePattern":"request failed \\(attempt (.+?)/(.+?)\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/gitlab/client.go","lineNumber":133,"sourceCode":"\t\tif body != nil {\n\t\t\tjsonBody, err := json.Marshal(body)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"failed to marshal request body: %w\", err)\n\t\t\t}\n\t\t\treqBody = bytes.NewReader(jsonBody)\n\t\t}\n\n\t\treq, err := http.NewRequestWithContext(ctx, method, urlStr, reqBody)\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"failed to create request: %w\", err)\n\t\t}\n\n\t\treq.Header.Set(\"PRIVATE-TOKEN\", c.Token)\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t\tresp, err := c.HTTPClient.Do(req)\n\t\tif err != nil {\n\t\t\tlastErr = fmt.Errorf(\"request failed (attempt %d/%d): %w\", attempt+1, MaxRetries+1, err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Limit response body to 50MB to prevent OOM from malformed responses.\n\t\tconst maxResponseSize = 50 * 1024 * 1024\n\t\trespBody, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))\n\t\t_ = resp.Body.Close() // Best effort: HTTP body close; connection may be reused regardless\n\t\tif err != nil {\n\t\t\tlastErr = fmt.Errorf(\"failed to read response (attempt %d/%d): %w\", attempt+1, MaxRetries+1, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif resp.StatusCode >= 200 && resp.StatusCode < 300 {\n\t\t\treturn respBody, resp.Header, nil\n\t\t}\n\n\t\t// Retry on rate-limiting and server errors with exponential backoff.\n\t\tretriable := resp.StatusCode == http.StatusTooManyRequests ||","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/gitlab/client.go#L115-L151","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check basic connectivity: curl -v https://your-gitlab/api/v4/version with your token","Verify DNS/proxy settings (HTTPS_PROXY env) and that the host resolves","Increase the HTTP client timeout if timeouts are the cause; check context deadlines upstream","If the final error says 'max retries exceeded', inspect the wrapped lastErr for the root transport cause"],"exampleFix":"// before\nclient := &http.Client{} // default, may time out on slow networks\n// after\nclient := &http.Client{Timeout: 60 * time.Second}","handlingStrategy":"retry","validationCode":"conn, err := net.DialTimeout(\"tcp\", host+\":443\", 5*time.Second)\nif err != nil { return fmt.Errorf(\"GitLab host unreachable: %w\", err) }\nconn.Close()","typeGuard":"var netErr net.Error\nif errors.As(err, &netErr) { /* transient network failure */ }","tryCatchPattern":"var netErr net.Error\nif err != nil && errors.As(err, &netErr) {\n\tif netErr.Timeout() { /* back off and retry at app level */ }\n}","preventionTips":["Set a sensible HTTP client timeout for your network conditions","Verify HTTPS_PROXY/NO_PROXY settings in CI and containers","Monitor instance health before blaming the client","Keep the client's built-in retries and add caller-level backoff for long outages"],"tags":["network","http","retry","gitlab"],"backgroundTag":"request-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}