{"record":{"id":"76a263533fdaf007","repo":"gastownhall/beads","slug":"failed-to-create-milestone-w","errorCode":null,"errorMessage":"failed to create milestone: %w","messagePattern":"failed to create milestone: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/gitlab/client.go","lineNumber":471,"sourceCode":"\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)\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","sourceCodeStart":453,"sourceCodeEnd":489,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/gitlab/client.go#L453-L489","documentation":"CreateMilestone POSTs to /projects/:id/milestones 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 400 (invalid/duplicate title), 401/403 (auth), or 422. It is raised before JSON parsing, so the write never completed.","triggerScenarios":"Calling Client.CreateMilestone with a title that already exists in the project (400), invalid/expired token (401/403), missing create permissions on the project, network outage, or a cancelled context.","commonSituations":"Duplicate milestone titles in the same project; token scoped without api/write access; read-only tokens used in automation; CI runners without egress to GitLab; project namespace misconfigured so the POST targets the wrong (or nonexistent) project.","solutions":["Unwrap the error to read the HTTP status; 400/422 typically means the title already exists or is invalid","Use a unique title (e.g. version-suffixed) and validate it is non-empty before calling","Ensure the token has api scope and Developer+ role with milestone-create permission on the project","Confirm the configured project path is correct and retry with backoff on transient transport errors"],"exampleFix":"// before\nm, err := client.CreateMilestone(ctx, title, desc)\nif err != nil { return err }\n// after\nif title == \"\" { return errors.New(\"milestone title required\") }\nm, err := client.CreateMilestone(ctx, title, desc)\nif err != nil {\n    log.Printf(\"create milestone %q failed: %v\", title, err)\n    return err\n}","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(title) == \"\" {\n    return errors.New(\"milestone title is required\")\n}\n// Pre-check duplicates to avoid a 400 from GitLab\nexisting, _ := client.FetchMilestones(ctx, \"active\")\nfor _, m := range existing {\n    if m.Title == title {\n        return fmt.Errorf(\"milestone %q already exists\", title)\n    }\n}","typeGuard":"func isConflict(err error) bool {\n    return strings.Contains(err.Error(), \"400\") || strings.Contains(err.Error(), \"422\")\n}","tryCatchPattern":"m, err := client.CreateMilestone(ctx, title, desc)\nif err != nil {\n    if isConflict(err) {\n        return findOrCreateMilestone(ctx, title, desc) // idempotent path\n    }\n    return fmt.Errorf(\"create milestone %q: %w\", title, err)\n}","preventionTips":["Check for an existing milestone with the same title before creating","Use tokens with api scope and Developer+ role on the target project","Validate the project path configuration so POSTs target the right project","Make creation idempotent in automation to tolerate retries"],"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"}