{"record":{"id":"55578089225ae18f","repo":"gastownhall/beads","slug":"request-failed-attempt-d-d-w-555780","errorCode":null,"errorMessage":"request failed (attempt %d/%d): %w","messagePattern":"request failed \\(attempt (.+?)/(.+?)\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/linear/client.go","lineNumber":378,"sourceCode":"\t\tif rlErr := c.circuitBreakerError(); rlErr != nil {\n\t\t\treturn nil, lastStatus, rlErr\n\t\t}\n\n\t\thttpReq, err := http.NewRequestWithContext(ctx, \"POST\", c.Endpoint, bytes.NewReader(body))\n\t\tif err != nil {\n\t\t\treturn nil, 0, fmt.Errorf(\"failed to create request: %w\", err)\n\t\t}\n\n\t\thttpReq.Header.Set(\"Content-Type\", \"application/json\")\n\t\tauthValue, err := c.authHeader()\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t\thttpReq.Header.Set(\"Authorization\", authValue)\n\n\t\tresp, err := c.HTTPClient.Do(httpReq)\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\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\tlastStatus = resp.StatusCode\n\t\trl := parseRateLimitHeaders(resp.Header)\n\t\tc.recordRateLimitHeaders(rl)\n\n\t\tif resp.StatusCode == http.StatusTooManyRequests {\n\t\t\tdelay := rl.RetryAfter\n\t\t\tif delay == 0 {\n\t\t\t\tdelay = RetryDelay * time.Duration(1<<attempt) // Exponential backoff","sourceCodeStart":360,"sourceCodeEnd":396,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/client.go#L360-L396","documentation":"This error wraps a transport-level failure from c.HTTPClient.Do() inside the Linear GraphQL client's retry loop (executeOnce, internal/linear/client.go:378). It means the HTTP request never completed — DNS, TCP, TLS, or context cancellation failed before a response status was available. The client retries up to MaxRetries+1 attempts; if all attempts fail, it is surfaced wrapped as 'max retries exceeded'.","triggerScenarios":"c.HTTPClient.Do returns a non-nil error: DNS resolution failure for api.linear.to, connection refused/timeout, TLS handshake failure, proxy misconfiguration, or the request context being canceled mid-request. Each failed attempt appends one of these wrapped errors until retries are exhausted.","commonSituations":"Offline or air-gapped environments, corporate proxies intercepting TLS to api.linear.to, VPN drops, very short context deadlines, invalid LINEAR_API_URL/endpoint configuration, or IPv6 connectivity issues in containers.","solutions":["Check basic connectivity: curl -v https://api.linear.to/graphql from the same host","Inspect the wrapped inner error (errors.Unwrap / %w chain) to distinguish DNS vs timeout vs TLS","Increase the context deadline or HTTPClient.Timeout if attempts die mid-flight","Fix proxy env vars (HTTPS_PROXY) or add the Linear endpoint to NO_PROXY","If offline, fix networking first — the client will keep retrying until MaxRetries is exhausted"],"exampleFix":"// before: no timeout, opaque transport failure\nclient := &linear.Client{Endpoint: \"https://api.linear.to/graphql\"}\ndata, err := client.Execute(ctx, req)\n// after: explicit timeout + unwrapping the cause\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\ndata, err := client.Execute(ctx, req)\nif err != nil {\n    var netErr net.Error\n    if errors.As(err, &netErr) && netErr.Timeout() {\n        log.Printf(\"linear request timed out: %v\", netErr)\n    }\n}","handlingStrategy":"retry","validationCode":"func checkLinearReachable(ctx context.Context) error {\n    c, cancel := context.WithTimeout(ctx, 5*time.Second)\n    defer cancel()\n    req, _ := http.NewRequestWithContext(c, http.MethodHead, \"https://api.linear.to\", nil)\n    resp, err := http.DefaultClient.Do(req)\n    if err != nil { return err }\n    resp.Body.Close()\n    return nil\n}","typeGuard":"func isTransportFailure(err error) bool {\n    var netErr net.Error\n    return errors.As(err, &netErr) || strings.Contains(err.Error(), \"request failed (attempt\")\n}","tryCatchPattern":"data, err := client.Execute(ctx, req)\nif err != nil {\n    if strings.Contains(err.Error(), \"request failed (attempt\") {\n        var netErr net.Error\n        if errors.As(err, &netErr) && netErr.Timeout() {\n            return fmt.Errorf(\"linear unreachable (timeout): %w\", err)\n        }\n        return fmt.Errorf(\"linear transport failure: %w\", err)\n    }\n    return err\n}","preventionTips":["Set an explicit HTTPClient.Timeout and context deadline before calling Execute","Check connectivity/DNS to api.linear.to in startup health checks","Configure HTTPS_PROXY/NO_PROXY correctly in corporate environments","Cap caller-side retries so total wait stays bounded"],"tags":["network","http","retry","linear-client"],"backgroundTag":"http-request-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}