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
- Unwrap the error to inspect the HTTP status; a 400 usually means an invalid state parameter
- Only pass "active", "closed", or "" (all) as the state argument
- Check token validity/scopes and the configured project path
- 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
- Only pass "active", "closed", or "" — not GitHub-style values like "open"
- Validate the project path configuration before bulk queries
- Keep PATs valid and scoped with read_api
- Retry transient transport errors with backoff rather than failing the whole run
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
- failed to fetch milestone by IID %d: %w
- request failed (attempt %d/%d): %w
- failed to fetch issue %d: %w
- failed to create milestone: %w
- failed to update milestone: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e5e2e8fdb6d896f9.
Report an issue: GitHub.