{"record":{"id":"5fbd70d61d10f1ee","repo":"gastownhall/beads","slug":"failed-to-parse-response-w-body-s","errorCode":null,"errorMessage":"failed to parse response: %w (body: %s)","messagePattern":"failed to parse response: %w \\(body: (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/linear/client.go","lineNumber":422,"sourceCode":"\t\t\tlastErr = fmt.Errorf(\"rate limited (attempt %d/%d), retrying after %v\", attempt+1, MaxRetries+1, delay)\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn nil, lastStatus, ctx.Err()\n\t\t\tcase <-time.After(delay):\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\t\treturn nil, lastStatus, fmt.Errorf(\"API error: %s (status %d)\", string(respBody), resp.StatusCode)\n\t\t}\n\n\t\tvar gqlResp struct {\n\t\t\tData   json.RawMessage `json:\"data\"`\n\t\t\tErrors []GraphQLError  `json:\"errors,omitempty\"`\n\t\t}\n\t\tif err := json.Unmarshal(respBody, &gqlResp); err != nil {\n\t\t\treturn nil, lastStatus, fmt.Errorf(\"failed to parse response: %w (body: %s)\", err, string(respBody))\n\t\t}\n\n\t\tif len(gqlResp.Errors) > 0 {\n\t\t\terrMsgs := make([]string, len(gqlResp.Errors))\n\t\t\tfor i, e := range gqlResp.Errors {\n\t\t\t\terrMsgs[i] = e.Message\n\t\t\t}\n\t\t\treturn nil, lastStatus, fmt.Errorf(\"GraphQL errors: %s\", strings.Join(errMsgs, \"; \"))\n\t\t}\n\n\t\treturn gqlResp.Data, lastStatus, nil\n\t}\n\n\treturn nil, lastStatus, fmt.Errorf(\"max retries (%d) exceeded: %w\", MaxRetries+1, lastErr)\n}\n\n// FetchIssues retrieves issues from Linear with optional filtering by state.\n// state can be: \"open\" (unstarted/started), \"closed\" (completed/canceled), or \"all\".","sourceCodeStart":404,"sourceCodeEnd":440,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/client.go#L404-L440","documentation":"The 2xx response body from Linear could not be unmarshaled into the expected GraphQL envelope {data, errors} (internal/linear/client.go:422). The request and transport succeeded, but the payload is not the JSON shape the client expects. The error includes the JSON parse error and the offending body for diagnosis. Not retried — returned immediately with the last status code.","triggerScenarios":"json.Unmarshal fails on a 2xx body: an HTML login/interstitial page, a proxy or captive portal replacing the JSON, a truncated body cut off by MaxResponseSize mid-JSON, or a gateway returning 200 with an error page.","commonSituations":"Corporate proxies injecting auth pages, misconfigured LINEAR_API_URL pointing at a UI route instead of the GraphQL endpoint, response size caps truncating large payloads, or CDN/WAF interference.","solutions":["Inspect the body embedded in the error — HTML means the endpoint is wrong or intercepted; 'unexpected end of JSON input' means truncation","Verify the endpoint is exactly https://api.linear.to/graphql","If truncation: shrink the query with pagination so the body fits under MaxResponseSize","Check proxy/WAF (HTTPS_PROXY, corporate gateways) for body rewriting and allowlist api.linear.to"],"exampleFix":"// before: endpoint pointing at the web UI\nclient := linear.NewClient(key)\nclient.Endpoint = \"https://linear.app\" // returns HTML 200 page\n_, err := client.Execute(ctx, req) // failed to parse response: invalid character '<' ...\n// after: correct GraphQL endpoint\nclient.Endpoint = \"https://api.linear.to/graphql\"\n_, err = client.Execute(ctx, req)","handlingStrategy":"validation","validationCode":"func checkEndpoint() error {\n    u, err := url.Parse(client.Endpoint)\n    if err != nil { return err }\n    if u.Host != \"api.linear.to\" || u.Path != \"/graphql\" {\n        return fmt.Errorf(\"unexpected Linear endpoint: %s\", client.Endpoint)\n    }\n    return nil\n}","typeGuard":"func isParseFailure(err error) bool {\n    return strings.Contains(err.Error(), \"failed to parse response:\")\n}\nfunc bodyLooksLikeHTML(body string) bool {\n    t := strings.TrimSpace(body)\n    return strings.HasPrefix(t, \"<\") || strings.Contains(t, \"<html\")\n}","tryCatchPattern":"_, err := client.Execute(ctx, req)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to parse response:\") {\n        if bodyLooksLikeHTML(err.Error()) {\n            return fmt.Errorf(\"linear endpoint returned HTML — check LINEAR_API_URL/proxy: %w\", err)\n        }\n        return fmt.Errorf(\"linear returned non-GraphQL body: %w\", err)\n    }\n    return err\n}","preventionTips":["Point the client strictly at https://api.linear.to/graphql, never a UI route","Keep queries paginated so bodies fit within MaxResponseSize (avoid truncation mid-JSON)","Check proxies/WAFs/captive portals that substitute HTML for JSON","Log the embedded body from the error to diagnose format issues quickly"],"tags":["json","parsing","http","linear-client"],"backgroundTag":"response-parse-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}