gastownhall/beads · error
failed to read response (attempt %d/%d): %w
Error message
failed to read response (attempt %d/%d): %w
What it means
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.
Source
Thrown at internal/linear/client.go:385
}
httpReq.Header.Set("Content-Type", "application/json")
authValue, err := c.authHeader()
if err != nil {
return nil, 0, err
}
httpReq.Header.Set("Authorization", authValue)
resp, err := c.HTTPClient.Do(httpReq)
if err != nil {
lastErr = fmt.Errorf("request failed (attempt %d/%d): %w", attempt+1, MaxRetries+1, err)
continue
}
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
}
lastStatus = resp.StatusCode
rl := parseRateLimitHeaders(resp.Header)
c.recordRateLimitHeaders(rl)
if resp.StatusCode == http.StatusTooManyRequests {
delay := rl.RetryAfter
if delay == 0 {
delay = RetryDelay * time.Duration(1<<attempt) // Exponential backoff
if half := int64(delay / 2); half > 0 {
delay += time.Duration(rand.Int64N(half)) //nolint:gosec // G404: jitter for retry backoff does not need crypto rand
}
} else if delay > MaxRetryAfterDelay {
fmt.Fprintf(os.Stderr, "linear: Retry-After %v exceeds cap %v; using cap\n", delay, MaxRetryAfterDelay)
delay = MaxRetryAfterDelay
}View on GitHub (pinned to 71377f2769)
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
Example fix
// before: unbounded query that streams a huge body
query := `{ issues { nodes { id title description comments { nodes { body } } } } }`
data, err := client.Execute(ctx, &linear.GraphQLRequest{Query: query})
// after: paginate to keep each response small
query := `query($after: String) { issues(first: 50, after: $after) { pageInfo { hasNextPage endCursor } nodes { id title } } }` Defensive patterns
Strategy: retry
Type guard
func isResponseReadError(err error) bool {
return strings.Contains(err.Error(), "failed to read response (attempt")
} Try / catch
data, err := client.Execute(ctx, req)
if err != nil {
if strings.Contains(err.Error(), "failed to read response") {
return retryWithBackoff(ctx, req) // transient mid-body break; retry once or twice
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to read response (attempt %d/%d): %w
- request failed (attempt %d/%d): %w
- request failed (attempt %d/%d): %w
- failed to list projects: %w
- pypi api returned status %d
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/bb4a70d3d118f18c.
Report an issue: GitHub.