{"record":{"id":"e98bf0f163c9254f","repo":"gastownhall/beads","slug":"failed-to-parse-milestones-response-w","errorCode":null,"errorMessage":"failed to parse milestones response: %w","messagePattern":"failed to parse milestones response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/gitlab/client.go","lineNumber":430,"sourceCode":"// FetchMilestones retrieves milestones from the project with optional state filter.\n// state can be: \"active\", \"closed\", or \"\" (all).\nfunc (c *Client) FetchMilestones(ctx context.Context, state string) ([]Milestone, error) {\n\tparams := map[string]string{\n\t\t\"per_page\": strconv.Itoa(MaxPageSize),\n\t}\n\tif state != \"\" {\n\t\tparams[\"state\"] = state\n\t}\n\n\turlStr := c.buildURL(\"/projects/\"+c.projectPath()+\"/milestones\", params)\n\trespBody, _, err := c.doRequest(ctx, http.MethodGet, urlStr, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to fetch milestones: %w\", err)\n\t}\n\n\tvar milestones []Milestone\n\tif err := json.Unmarshal(respBody, &milestones); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse milestones response: %w\", err)\n\t}\n\n\treturn milestones, nil\n}\n\n// FetchMilestoneByIID retrieves a single milestone by its project-scoped IID.\n// Returns nil if no milestone matches the given IID.\nfunc (c *Client) FetchMilestoneByIID(ctx context.Context, iid int) (*Milestone, error) {\n\tparams := map[string]string{\n\t\t\"iids[]\": strconv.Itoa(iid),\n\t}\n\n\turlStr := c.buildURL(\"/projects/\"+c.projectPath()+\"/milestones\", params)\n\trespBody, _, err := c.doRequest(ctx, http.MethodGet, urlStr, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to fetch milestone by IID %d: %w\", iid, err)\n\t}\n","sourceCodeStart":412,"sourceCodeEnd":448,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/gitlab/client.go#L412-L448","documentation":"After FetchMilestones receives a successful HTTP response from /projects/:id/milestones, the body is unmarshaled into []Milestone. This error wraps the json.Unmarshal failure when the body is not a valid JSON array of milestones or contains values that conflict with the Milestone struct. The HTTP call itself succeeded, so the payload is malformed or unexpected.","triggerScenarios":"Calling Client.FetchMilestones when a proxy/SSO returns HTML, the response is empty/truncated, or a GitLab version emits milestone fields whose JSON types clash with the Milestone struct (e.g. null vs string).","commonSituations":"Reverse proxies injecting HTML error pages; self-managed GitLab with a divergent milestones payload; wrong base URL landing on a non-API route; encoding/charset issues from an intermediary.","solutions":["Log the raw body and the wrapped json.SyntaxError/*json.UnmarshalTypeError to see what the server actually returned","Verify the base URL points at the GitLab API host, not the web UI or an SSO redirect","Align the Milestone struct json tags/types with your GitLab version's payload","Retry if a transient gateway error could have truncated the response"],"exampleFix":"// before\nms, err := client.FetchMilestones(ctx, \"\")\nif err != nil { return nil, err }\n// after\nms, err := client.FetchMilestones(ctx, \"\")\nif err != nil {\n    var te *json.UnmarshalTypeError\n    if errors.As(err, &te) {\n        log.Printf(\"milestone payload mismatch at %s: %v\", te.Field, te.Error())\n    }\n    return nil, err\n}","handlingStrategy":"type-guard","validationCode":"// Ensure API base URL targets the GitLab API, not a proxy/web UI\nu, err := url.Parse(gitlabHost)\nif err != nil || u.Host == \"\" {\n    return fmt.Errorf(\"invalid gitlab host %q\", gitlabHost)\n}","typeGuard":"func isMalformedResponse(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":"ms, err := client.FetchMilestones(ctx, state)\nif err != nil {\n    if isMalformedResponse(err) {\n        return nil, fmt.Errorf(\"gitlab returned malformed milestones payload: %w\", err)\n    }\n    return nil, err\n}","preventionTips":["Exclude the GitLab API host from SSO/proxy HTML injection","Log the raw body on parse failure to identify proxies or truncation","Keep the Milestone struct's json tags matched to your GitLab version","Monitor GitLab release notes for milestones API payload changes"],"tags":["gitlab","json","api-response","milestones"],"backgroundTag":"malformed-json-response","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}