{"record":{"id":"58f8c846687ac960","repo":"gastownhall/beads","slug":"failed-to-read-response-w","errorCode":null,"errorMessage":"failed to read response: %w","messagePattern":"failed to read response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/linear/client.go","lineNumber":901,"sourceCode":"\t\treturn nil, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\thttpReq.Header.Set(\"Content-Type\", \"application/json\")\n\tauthValue, err := c.authHeader()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\thttpReq.Header.Set(\"Authorization\", authValue)\n\n\tresp, err := c.HTTPClient.Do(httpReq)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"request failed: %w\", err)\n\t}\n\n\trespBody, err := io.ReadAll(io.LimitReader(resp.Body, MaxResponseSize))\n\t_ = resp.Body.Close()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read response: %w\", err)\n\t}\n\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\treturn nil, fmt.Errorf(\"API error: %s (status %d)\", string(respBody), resp.StatusCode)\n\t}\n\n\tvar gqlResp struct {\n\t\tData   json.RawMessage `json:\"data\"`\n\t\tErrors []GraphQLError  `json:\"errors,omitempty\"`\n\t}\n\tif err := json.Unmarshal(respBody, &gqlResp); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse response: %w (body: %s)\", err, string(respBody))\n\t}\n\n\tif len(gqlResp.Errors) > 0 {\n\t\terrMsgs := make([]string, len(gqlResp.Errors))\n\t\tfor i, e := range gqlResp.Errors {\n\t\t\terrMsgs[i] = e.Message","sourceCodeStart":883,"sourceCodeEnd":919,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/client.go#L883-L919","documentation":"This error is returned when io.ReadAll fails while reading the response body from Linear, limited to MaxResponseSize via io.LimitReader. It is uncommon because reading from an in-memory buffered HTTP body rarely fails; it usually means the connection was reset or dropped mid-read (truncated response).","triggerScenarios":"Connection reset by proxy/load-balancer while streaming the GraphQL response; response aborted after headers were received; TLS truncation; LimitReader boundary interacting with a broken chunked encoding.","commonSituations":"Flaky networks or mobile/VPN links; intermediaries (corporate proxies, AWS ALB) closing idle connections prematurely; very large error responses being cut off.","solutions":["Retry the request — this is almost always transient","Check proxy/load-balancer idle timeout settings between the client and api.linear.app","Log the wrapped error (errors.Unwrap) to confirm it is an unexpected-EOF/reset style failure","Keep MaxResponseSize reasonable; ensure the caller's network path is stable (avoid aggressive connection reuse through NAT)"],"exampleFix":"// before\ndata, _, err := c.CreateIssueIdempotent(ctx, ...) // transient read failure aborts flow\nif err != nil { return err }\n// after\nvar issue *linear.Issue\nerr := retry.Do(3, backoff, func() error {\n    var exists bool\n    issue, exists, err = c.CreateIssueIdempotent(ctx, ...)\n    return err\n})","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"failed to read response\") {\n    // transient body read failure: safe to retry the whole call\n    return retryWithBackoff(call)\n}","preventionTips":["Always retry idempotent Linear calls on read failures","Monitor for connection resets at the proxy/load-balancer layer","Avoid long-lived keep-alive connections through aggressive NATs","Keep request/response sizes well under MaxResponseSize"],"tags":["network","io","transient"],"backgroundTag":"response-read-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}