{"record":{"id":"bb4a70d3d118f18c","repo":"gastownhall/beads","slug":"failed-to-read-response-attempt-d-d-w-bb4a70","errorCode":null,"errorMessage":"failed to read response (attempt %d/%d): %w","messagePattern":"failed to read response \\(attempt (.+?)/(.+?)\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/linear/client.go","lineNumber":385,"sourceCode":"\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\n\t\t\t\tif half := int64(delay / 2); half > 0 {\n\t\t\t\t\tdelay += time.Duration(rand.Int64N(half)) //nolint:gosec // G404: jitter for retry backoff does not need crypto rand\n\t\t\t\t}\n\t\t\t} else if delay > MaxRetryAfterDelay {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"linear: Retry-After %v exceeds cap %v; using cap\\n\", delay, MaxRetryAfterDelay)\n\t\t\t\tdelay = MaxRetryAfterDelay\n\t\t\t}","sourceCodeStart":367,"sourceCodeEnd":403,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/client.go#L367-L403","documentation":"io.ReadAll failed while draining the Linear GraphQL response body, capped at MaxResponseSize via io.LimitReader (internal/linear/client.go:385). The HTTP round trip succeeded, but the response stream broke mid-read. Like transport errors, this is retried up to MaxRetries+1 times and only surfaced if every attempt fails.","triggerScenarios":"The connection is reset or times out while streaming the response body, the server closes the connection prematurely, or a proxy/keep-alive race truncates the stream so io.ReadAll returns an error.","commonSituations":"Flaky networks or mobile/VPN links, aggressive load balancers with short idle timeouts, large GraphQL payloads (big issue exports) exceeding proxy body limits, or keep-alive connections reused after a long idle period.","solutions":["Retry the operation — transient mid-body resets usually succeed on a fresh connection","Reduce response size with pagination/limits in the GraphQL query","Check for proxies or load balancers truncating large responses","Inspect the wrapped error for 'connection reset' vs 'context deadline exceeded' and fix accordingly"],"exampleFix":"// before: unbounded query that streams a huge body\nquery := `{ issues { nodes { id title description comments { nodes { body } } } } }`\ndata, err := client.Execute(ctx, &linear.GraphQLRequest{Query: query})\n// after: paginate to keep each response small\nquery := `query($after: String) { issues(first: 50, after: $after) { pageInfo { hasNextPage endCursor } nodes { id title } } }`","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isResponseReadError(err error) bool {\n    return strings.Contains(err.Error(), \"failed to read response (attempt\")\n}","tryCatchPattern":"data, err := client.Execute(ctx, req)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to read response\") {\n        return retryWithBackoff(ctx, req) // transient mid-body break; retry once or twice\n    }\n    return err\n}","preventionTips":["Paginate large queries so response bodies stay small and quick to stream","Avoid reused idle connections through proxies with short idle timeouts — keep the client's requests flowing","Retry once on this error; it is almost always transient","Watch for MaxResponseSize truncation with very large payloads"],"tags":["network","http","io","retry","linear-client"],"backgroundTag":"response-read-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}