gastownhall/beads · error

failed to update milestone: %w

Error message

failed to update milestone: %w

What it means

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.

Source

Thrown at internal/gitlab/client.go:487

	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)
	}

	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
}

// GraphQL support for work item hierarchy (Issue → Task parent-child).

// graphqlRequest executes a GraphQL query against the GitLab instance.
func (c *Client) graphqlRequest(ctx context.Context, query string, variables map[string]interface{}) (json.RawMessage, error) {
	body := map[string]interface{}{"query": query}
	if len(variables) > 0 {
		body["variables"] = variables
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Unwrap the error to read the HTTP status; 404 means verify the milestoneID belongs to the configured project
  2. Validate the updates map keys against GitLab's milestone update API (title, description, state_event, due_date, start_date)
  3. Ensure the token has api scope and sufficient role to edit milestones
  4. Retry with backoff for 5xx/transport errors after confirming connectivity to the GitLab host

Example fix

// before
m, err := client.UpdateMilestone(ctx, id, map[string]interface{}{"status": "done"}) // invalid key
// after
updates := map[string]interface{}{"state_event": "close"} // valid GitLab field
m, err := client.UpdateMilestone(ctx, id, updates)
if err != nil { return fmt.Errorf("update milestone %d: %w", id, err) }
Defensive patterns

Strategy: validation

Validate before calling

// Whitelist updatable fields and require a known milestone ID
var allowed = map[string]bool{"title": true, "description": true, "due_date": true, "start_date": true, "state_event": true}
for k := range updates {
    if !allowed[k] {
        return fmt.Errorf("unsupported milestone update field %q", k)
    }
}
if milestoneID <= 0 {
    return fmt.Errorf("invalid milestone ID %d", milestoneID)
}

Type guard

func isNotFound(err error) bool {
    return strings.Contains(err.Error(), "404")
}

Try / catch

m, err := client.UpdateMilestone(ctx, id, updates)
if err != nil {
    if isNotFound(err) {
        return fmt.Errorf("milestone %d not in this project: %w", id, err) // don't retry
    }
    if isTransient(err) {
        return updateWithRetry(ctx, id, updates)
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/0adfea826ada36e2. Report an issue: GitHub.