{"record":{"id":"d717a7c96c1b69b6","repo":"gastownhall/beads","slug":"failed-to-parse-issue-links-response-w","errorCode":null,"errorMessage":"failed to parse issue links response: %w","messagePattern":"failed to parse issue links response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/gitlab/client.go","lineNumber":390,"sourceCode":"\tvar issue Issue\n\tif err := json.Unmarshal(respBody, &issue); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse update response: %w\", err)\n\t}\n\n\treturn &issue, nil\n}\n\n// GetIssueLinks retrieves issue links for the specified issue IID.\nfunc (c *Client) GetIssueLinks(ctx context.Context, iid int) ([]IssueLink, error) {\n\turlStr := c.buildURL(\"/projects/\"+c.projectPath()+\"/issues/\"+strconv.Itoa(iid)+\"/links\", nil)\n\trespBody, _, err := c.doRequest(ctx, http.MethodGet, urlStr, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get issue links: %w\", err)\n\t}\n\n\tvar links []IssueLink\n\tif err := json.Unmarshal(respBody, &links); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse issue links response: %w\", err)\n\t}\n\n\treturn links, nil\n}\n\n// FetchIssueByIID retrieves a single issue by its project-scoped IID.\nfunc (c *Client) FetchIssueByIID(ctx context.Context, iid int) (*Issue, error) {\n\turlStr := c.buildURL(\"/projects/\"+c.projectPath()+\"/issues/\"+strconv.Itoa(iid), nil)\n\trespBody, _, err := c.doRequest(ctx, http.MethodGet, urlStr, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to fetch issue %d: %w\", iid, err)\n\t}\n\n\tvar issue Issue\n\tif err := json.Unmarshal(respBody, &issue); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse issue response: %w\", err)\n\t}\n","sourceCodeStart":372,"sourceCodeEnd":408,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/gitlab/client.go#L372-L408","documentation":"GetIssueLinks in the GitLab client calls the GitLab REST endpoint /projects/:id/issues/:iid/links and unmarshals the response body into []IssueLink. This error wraps the json.Unmarshal failure when the HTTP response body is not a valid JSON array of issue links. It only fires after doRequest succeeded (HTTP completed and was not treated as an API error), so the body arrived but was malformed or an unexpected shape.","triggerScenarios":"Calling Client.GetIssueLinks when the GitLab server (or a proxy/gateway in front of it) returns HTML (login page, error page), an empty/truncated body, or JSON whose top level is an object instead of an array of IssueLink.","commonSituations":"Corporate proxies or SSO redirects injecting HTML into API responses; reverse proxies returning 502/504 error pages as HTML with a 200-like path; GitLab version changes altering the links payload; hitting a rate-limit page instead of the JSON API.","solutions":["Log the raw response body and the wrapped json.Unmarshal error (e.g. errors.Unwrap) to see whether the body is HTML, empty, or differently shaped","Verify the request is reaching the real GitLab API host and not an SSO/proxy redirect; check GITLAB_HOST/token configuration","Re-run with a curl of the same endpoint to confirm the API returns a JSON array","Retry after transient gateway errors; if a GitLab upgrade changed the schema, update the IssueLink struct to match the actual payload"],"exampleFix":"// before\nlinks, err := client.GetIssueLinks(ctx, 42)\nif err != nil {\n    return err\n}\n// after\nlinks, err := client.GetIssueLinks(ctx, 42)\nif err != nil {\n    var jsonErr *json.UnmarshalTypeError\n    if errors.As(err, &jsonErr) {\n        log.Printf(\"non-JSON or unexpected payload from links endpoint: %v\", jsonErr)\n    }\n    return fmt.Errorf(\"GetIssueLinks: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"// Ensure the GitLab host/token are set before any API call\nif client == nil || strings.TrimSpace(gitlabHost) == \"\" || strings.TrimSpace(token) == \"\" {\n    return errors.New(\"gitlab host/token must be configured\")\n}","typeGuard":"func isParseFailure(err error) bool {\n    var se *json.SyntaxError\n    var te *json.UnmarshalTypeError\n    return errors.As(err, &se) || errors.As(err, &te)\n}","tryCatchPattern":"links, err := client.GetIssueLinks(ctx, iid)\nif err != nil {\n    if isParseFailure(err) {\n        // response was non-JSON (proxy/SSO page) — do not retry blindly\n        return fmt.Errorf(\"gitlab returned non-JSON links payload: %w\", err)\n    }\n    return err\n}","preventionTips":["Point the client base URL at the API host, never the web UI or an SSO-protected page","Log response bodies on parse failures to distinguish HTML proxies from schema drift","Keep the IssueLink struct aligned with your GitLab version's API","Check for proxy/VPN interference when errors cluster in one network environment"],"tags":["gitlab","json","network","api-response"],"backgroundTag":"malformed-json-response","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}