gastownhall/beads · error

failed to fetch milestones: %w

Error message

failed to fetch milestones: %w

What it means

FetchMilestones GETs /projects/:id/milestones through doRequest, which manages authentication, retries, and HTTP status handling. This error wraps any doRequest failure — network errors, timeouts, context cancellation, or HTTP error statuses from GitLab. It is raised before JSON parsing, so the request never produced a usable response.

Source

Thrown at internal/gitlab/client.go:425

	}

	return &issue, nil
}

// FetchMilestones retrieves milestones from the project with optional state filter.
// state can be: "active", "closed", or "" (all).
func (c *Client) FetchMilestones(ctx context.Context, state string) ([]Milestone, error) {
	params := map[string]string{
		"per_page": strconv.Itoa(MaxPageSize),
	}
	if state != "" {
		params["state"] = state
	}

	urlStr := c.buildURL("/projects/"+c.projectPath()+"/milestones", params)
	respBody, _, err := c.doRequest(ctx, http.MethodGet, urlStr, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch milestones: %w", err)
	}

	var milestones []Milestone
	if err := json.Unmarshal(respBody, &milestones); err != nil {
		return nil, fmt.Errorf("failed to parse milestones response: %w", err)
	}

	return milestones, nil
}

// FetchMilestoneByIID retrieves a single milestone by its project-scoped IID.
// Returns nil if no milestone matches the given IID.
func (c *Client) FetchMilestoneByIID(ctx context.Context, iid int) (*Milestone, error) {
	params := map[string]string{
		"iids[]": strconv.Itoa(iid),
	}

	urlStr := c.buildURL("/projects/"+c.projectPath()+"/milestones", params)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Unwrap the error to inspect the HTTP status; a 400 usually means an invalid state parameter
  2. Only pass "active", "closed", or "" (all) as the state argument
  3. Check token validity/scopes and the configured project path
  4. For transport errors, verify connectivity to the GitLab host and retry with backoff

Example fix

// before
ms, err := client.FetchMilestones(ctx, "open") // invalid state
// after
state := "open"
if state != "active" && state != "closed" { state = "" }
ms, err := client.FetchMilestones(ctx, state)
Defensive patterns

Strategy: validation

Validate before calling

func validMilestoneState(s string) bool {
    return s == "" || s == "active" || s == "closed"
}
// before calling:
if !validMilestoneState(state) {
    return fmt.Errorf("invalid milestone state %q (use active, closed, or empty)", state)
}

Try / catch

ms, err := client.FetchMilestones(ctx, state)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        return fetchMilestonesWithRetry(ctx, state)
    }
    return fmt.Errorf("list milestones: %w", err)
}

Prevention

When it happens

Trigger: Calling Client.FetchMilestones with an invalid state value (GitLab rejects it with 400), bad/expired credentials (401/403), wrong project path, network outage, or a cancelled context.

Common situations: Passing a state other than active/closed (e.g. "open", which is GitHub-style); PAT expired or missing read_api scope; misconfigured project namespace; CI environments without network egress to GitLab.

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/e5e2e8fdb6d896f9. Report an issue: GitHub.