{"record":{"id":"17de3e3727c681f7","repo":"gastownhall/beads","slug":"failed-to-parse-milestone-response-w","errorCode":null,"errorMessage":"failed to parse milestone response: %w","messagePattern":"failed to parse milestone response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/gitlab/client.go","lineNumber":451,"sourceCode":"\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\n\tvar milestones []Milestone\n\tif err := json.Unmarshal(respBody, &milestones); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse milestone response: %w\", err)\n\t}\n\n\tif len(milestones) == 0 {\n\t\treturn nil, nil\n\t}\n\n\treturn &milestones[0], nil\n}\n\n// CreateMilestone creates a new milestone in GitLab.\nfunc (c *Client) CreateMilestone(ctx context.Context, title, description string) (*Milestone, error) {\n\tbody := map[string]interface{}{\n\t\t\"title\":       title,\n\t\t\"description\": description,\n\t}\n\n\turlStr := c.buildURL(\"/projects/\"+c.projectPath()+\"/milestones\", nil)\n\trespBody, _, err := c.doRequest(ctx, http.MethodPost, urlStr, body)","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/gitlab/client.go#L433-L469","documentation":"After FetchMilestoneByIID gets a successful response from /projects/:id/milestones?iids[]=N, the body is unmarshaled into []Milestone. This error wraps the json.Unmarshal failure when the body is not a valid JSON array matching the Milestone struct. Note FetchMilestoneByIID itself returns (nil, nil) for an empty array, so this error is strictly about malformed payloads, not 'not found'.","triggerScenarios":"Calling Client.FetchMilestoneByIID when a proxy/SSO injects HTML into the response, the body is empty/truncated, or a GitLab version emits milestone field types that conflict with the Milestone struct (e.g. number-vs-string IDs).","commonSituations":"Corporate proxies or error pages returned with 200 responses; self-managed GitLab versions with divergent milestone schemas; wrong base URL hitting a non-API route; truncated responses from flaky gateways.","solutions":["Log the raw response body and the wrapped json error (SyntaxError vs UnmarshalTypeError) to identify the mismatch","Verify the base URL targets the GitLab API host, not a web UI or SSO redirect","Update the Milestone struct json tags/types to match your GitLab version's actual payload","Retry if a transient gateway error could have truncated the body"],"exampleFix":"// before\nm, err := client.FetchMilestoneByIID(ctx, iid)\n// after\nm, err := client.FetchMilestoneByIID(ctx, iid)\nif err != nil {\n    var se *json.SyntaxError\n    if errors.As(err, &se) {\n        log.Printf(\"non-JSON milestone payload (offset %d): likely HTML from proxy\", se.Offset)\n    }\n    return err\n}","handlingStrategy":"type-guard","validationCode":"// Validate API host configuration to avoid proxy-injected HTML\nif u, err := url.Parse(gitlabHost); err != nil || u.Scheme == \"\" || u.Host == \"\" {\n    return fmt.Errorf(\"misconfigured gitlab host: %q\", gitlabHost)\n}","typeGuard":"func isPayloadError(err error) bool {\n    var se *json.SyntaxError\n    var te *json.UnmarshalTypeError\n    return errors.As(err, &se) || errors.As(err, &te)\n}\n// Also narrow the nil result:\nfunc milestoneFound(m *Milestone) bool { return m != nil && m.ID != 0 }","tryCatchPattern":"m, err := client.FetchMilestoneByIID(ctx, iid)\nif err != nil {\n    if isPayloadError(err) {\n        return fmt.Errorf(\"malformed milestone payload: %w\", err)\n    }\n    return err\n}\nif !milestoneFound(m) {\n    return nil // legitimately not found — function returns nil,nil\n}","preventionTips":["Remember FetchMilestoneByIID returns (nil, nil) when not found — always nil-check the result","Keep the Milestone struct in sync with your GitLab version","Bypass proxies/SSO for API traffic to avoid HTML responses","Log raw bodies on parse errors to diagnose gateway truncation"],"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"}