gastownhall/beads · error

failed to fetch milestone by IID %d: %w

Error message

failed to fetch milestone by IID %d: %w

What it means

FetchMilestoneByIID queries /projects/:id/milestones with an iids[] filter via doRequest, which handles auth, retries, and HTTP errors. This error wraps any doRequest failure — network errors, timeouts, context cancellation, or HTTP error statuses (401/403/500) returned by GitLab. It is raised before JSON parsing, so the request never completed successfully.

Source

Thrown at internal/gitlab/client.go:446

	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)
	respBody, _, err := c.doRequest(ctx, http.MethodGet, urlStr, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch milestone by IID %d: %w", iid, err)
	}

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

	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,

View on GitHub (pinned to 71377f2769)

Solutions

  1. Unwrap the error to read the HTTP status or transport cause (url.Error, context.DeadlineExceeded)
  2. Check token validity and read_api scope; re-authenticate if 401/403
  3. Verify the client's configured project path matches the milestone's project
  4. For timeouts, increase the client timeout or retry; confirm network egress to the GitLab host

Example fix

// before
m, err := client.FetchMilestoneByIID(ctx, iid)
if err != nil { return err }
// after
m, err := client.FetchMilestoneByIID(ctx, iid)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        return retryWithBackoff(...)
    }
    return fmt.Errorf("milestone %d lookup: %w", iid, err)
}
Defensive patterns

Strategy: retry

Validate before calling

if iid <= 0 {
    return fmt.Errorf("invalid milestone IID %d", iid)
}
if err := ctx.Err(); err != nil {
    return err
}

Type guard

func isAuthError(err error) bool {
    return strings.Contains(err.Error(), "401") || strings.Contains(err.Error(), "403")
}

Try / catch

m, err := client.FetchMilestoneByIID(ctx, iid)
if err != nil {
    if isAuthError(err) {
        return fmt.Errorf("check GITLAB token/scopes: %w", err) // don't retry auth
    }
    if isTransient(err) {
        return fetchMilestoneWithRetry(ctx, iid)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Client.FetchMilestoneByIID with expired/invalid credentials, a misconfigured project path, network outage, or a cancelled context while the filtered milestones query runs.

Common situations: PAT expired or lacking read_api scope; project namespace changed/renamed so the URL 404s; VPN or proxy blocking the GitLab host in CI; context deadline exceeded on slow GitLab instances.

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