{"record":{"id":"0adfea826ada36e2","repo":"gastownhall/beads","slug":"failed-to-update-milestone-w","errorCode":null,"errorMessage":"failed to update milestone: %w","messagePattern":"failed to update milestone: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/gitlab/client.go","lineNumber":487,"sourceCode":"\trespBody, _, err := c.doRequest(ctx, http.MethodPost, urlStr, body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create milestone: %w\", err)\n\t}\n\n\tvar milestone Milestone\n\tif err := json.Unmarshal(respBody, &milestone); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse milestone response: %w\", err)\n\t}\n\n\treturn &milestone, nil\n}\n\n// UpdateMilestone updates an existing milestone in GitLab.\nfunc (c *Client) UpdateMilestone(ctx context.Context, milestoneID int, updates map[string]interface{}) (*Milestone, error) {\n\turlStr := c.buildURL(\"/projects/\"+c.projectPath()+\"/milestones/\"+strconv.Itoa(milestoneID), nil)\n\trespBody, _, err := c.doRequest(ctx, http.MethodPut, urlStr, updates)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to update milestone: %w\", err)\n\t}\n\n\tvar milestone Milestone\n\tif err := json.Unmarshal(respBody, &milestone); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse milestone response: %w\", err)\n\t}\n\n\treturn &milestone, nil\n}\n\n// GraphQL support for work item hierarchy (Issue → Task parent-child).\n\n// graphqlRequest executes a GraphQL query against the GitLab instance.\nfunc (c *Client) graphqlRequest(ctx context.Context, query string, variables map[string]interface{}) (json.RawMessage, error) {\n\tbody := map[string]interface{}{\"query\": query}\n\tif len(variables) > 0 {\n\t\tbody[\"variables\"] = variables\n\t}","sourceCodeStart":469,"sourceCodeEnd":505,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/gitlab/client.go#L469-L505","documentation":"UpdateMilestone PUTs to /projects/:id/milestones/:id through doRequest, which handles authentication, retries, and HTTP error statuses. This error wraps any doRequest failure — network errors, timeouts, context cancellation, or HTTP errors such as 404 (bad milestoneID), 400 (invalid update fields), or 401/403. It is raised before JSON parsing, so the update never completed.","triggerScenarios":"Calling Client.UpdateMilestone with a milestoneID that does not exist in the configured project (404), invalid update keys/values (400), a token lacking write access (401/403), network outage, or a cancelled context.","commonSituations":"Using a milestone ID from a different project; sending unsupported field names in the updates map; token without api scope; GitLab maintenance windows causing 5xx; CI environments without network egress.","solutions":["Unwrap the error to read the HTTP status; 404 means verify the milestoneID belongs to the configured project","Validate the updates map keys against GitLab's milestone update API (title, description, state_event, due_date, start_date)","Ensure the token has api scope and sufficient role to edit milestones","Retry with backoff for 5xx/transport errors after confirming connectivity to the GitLab host"],"exampleFix":"// before\nm, err := client.UpdateMilestone(ctx, id, map[string]interface{}{\"status\": \"done\"}) // invalid key\n// after\nupdates := map[string]interface{}{\"state_event\": \"close\"} // valid GitLab field\nm, err := client.UpdateMilestone(ctx, id, updates)\nif err != nil { return fmt.Errorf(\"update milestone %d: %w\", id, err) }","handlingStrategy":"validation","validationCode":"// Whitelist updatable fields and require a known milestone ID\nvar allowed = map[string]bool{\"title\": true, \"description\": true, \"due_date\": true, \"start_date\": true, \"state_event\": true}\nfor k := range updates {\n    if !allowed[k] {\n        return fmt.Errorf(\"unsupported milestone update field %q\", k)\n    }\n}\nif milestoneID <= 0 {\n    return fmt.Errorf(\"invalid milestone ID %d\", milestoneID)\n}","typeGuard":"func isNotFound(err error) bool {\n    return strings.Contains(err.Error(), \"404\")\n}","tryCatchPattern":"m, err := client.UpdateMilestone(ctx, id, updates)\nif err != nil {\n    if isNotFound(err) {\n        return fmt.Errorf(\"milestone %d not in this project: %w\", id, err) // don't retry\n    }\n    if isTransient(err) {\n        return updateWithRetry(ctx, id, updates)\n    }\n    return err\n}","preventionTips":["Resolve milestone IDs in the same project you configured on the client","Restrict the updates map to documented GitLab milestone fields (e.g. state_event: close)","Use tokens with api scope and edit permission on milestones","Retry only transient 5xx/transport errors with backoff; fail fast on 4xx"],"tags":["gitlab","http","write-operation","milestones"],"backgroundTag":"http-request-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}