gastownhall/beads · error
failed to create milestone: %w
Error message
failed to create milestone: %w
What it means
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.
Source
Thrown at internal/gitlab/client.go:471
if len(milestones) == 0 {
return nil, nil
}
return &milestones[0], nil
}
// CreateMilestone creates a new milestone in GitLab.
func (c *Client) CreateMilestone(ctx context.Context, title, description string) (*Milestone, error) {
body := map[string]interface{}{
"title": title,
"description": description,
}
urlStr := c.buildURL("/projects/"+c.projectPath()+"/milestones", nil)
respBody, _, err := c.doRequest(ctx, http.MethodPost, urlStr, body)
if err != nil {
return nil, fmt.Errorf("failed to create milestone: %w", err)
}
var milestone Milestone
if err := json.Unmarshal(respBody, &milestone); err != nil {
return nil, fmt.Errorf("failed to parse milestone response: %w", err)
}
return &milestone, nil
}
// UpdateMilestone updates an existing milestone in GitLab.
func (c *Client) UpdateMilestone(ctx context.Context, milestoneID int, updates map[string]interface{}) (*Milestone, error) {
urlStr := c.buildURL("/projects/"+c.projectPath()+"/milestones/"+strconv.Itoa(milestoneID), nil)
respBody, _, err := c.doRequest(ctx, http.MethodPut, urlStr, updates)
if err != nil {
return nil, fmt.Errorf("failed to update milestone: %w", err)
}
View on GitHub (pinned to 71377f2769)
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
Example fix
// before
m, err := client.CreateMilestone(ctx, title, desc)
if err != nil { return err }
// after
if title == "" { return errors.New("milestone title required") }
m, err := client.CreateMilestone(ctx, title, desc)
if err != nil {
log.Printf("create milestone %q failed: %v", title, err)
return err
} Defensive patterns
Strategy: validation
Validate before calling
if strings.TrimSpace(title) == "" {
return errors.New("milestone title is required")
}
// Pre-check duplicates to avoid a 400 from GitLab
existing, _ := client.FetchMilestones(ctx, "active")
for _, m := range existing {
if m.Title == title {
return fmt.Errorf("milestone %q already exists", title)
}
} Type guard
func isConflict(err error) bool {
return strings.Contains(err.Error(), "400") || strings.Contains(err.Error(), "422")
} Try / catch
m, err := client.CreateMilestone(ctx, title, desc)
if err != nil {
if isConflict(err) {
return findOrCreateMilestone(ctx, title, desc) // idempotent path
}
return fmt.Errorf("create milestone %q: %w", title, err)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- failed to update milestone: %w
- failed to fetch milestones: %w
- failed to fetch milestone by IID %d: %w
- request failed (attempt %d/%d): %w
- failed to fetch issue %d: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/76a263533fdaf007.
Report an issue: GitHub.