gastownhall/beads · error
failed to fetch issues: %w
Error message
failed to fetch issues: %w
What it means
FetchIssues calls doRequest to GET the issues endpoint; any failure inside doRequest (auth error, network failure, retries exhausted, malformed request) is wrapped as 'failed to fetch issues'. This is the outer context wrapper - the real cause is in the wrapped chain (e.g. errors 1773-1778).
Source
Thrown at internal/gitlab/client.go:254
select {
case <-ctx.Done():
return allIssues, ctx.Err()
default:
}
params := map[string]string{
"per_page": strconv.Itoa(MaxPageSize),
"page": strconv.Itoa(page),
}
if state != "" && state != "all" {
params["state"] = state
}
applyFilter(params, filter)
urlStr := c.buildURL(c.issuesBasePath(), params)
respBody, headers, err := c.doRequest(ctx, http.MethodGet, urlStr, nil)
if err != nil {
return nil, fmt.Errorf("failed to fetch issues: %w", err)
}
var issues []Issue
if err := json.Unmarshal(respBody, &issues); err != nil {
return nil, fmt.Errorf("failed to parse issues response: %w", err)
}
allIssues = append(allIssues, issues...)
// Check for next page
nextPage := headers.Get("X-Next-Page")
if nextPage == "" {
break
}
page++
// Guard against infinite pagination loops from malformed responses
if page > MaxPages {View on GitHub (pinned to 71377f2769)
Solutions
- Print the full error chain (err with %v or errors.Unwrap) - the wrapped cause names the real problem (status code, network error)
- Validate the token: curl -H "PRIVATE-TOKEN: $TOKEN" https://gitlab.example/api/v4/user
- Verify the configured project path/group resolves in the GitLab UI and is URL-encoded for subgroups
- Check connectivity to the instance and whether a 429/5xx outage explains retry exhaustion
Example fix
// before
issues, err := client.FetchIssues(ctx, filter)
log.Println(err) // failed to fetch issues: API error: 401 ...
// after
issues, err := client.FetchIssues(ctx, filter)
if err != nil {
var apiErr *gitlab.APIError // or check strings for status
log.Printf("fetch failed: %v", err) // log full chain
} Defensive patterns
Strategy: try-catch
Validate before calling
if !validBaseURL(cfg.BaseURL) || cfg.Token == "" {
return fmt.Errorf("GitLab client misconfigured before FetchIssues")
} Type guard
func fetchFailed(err error) bool { return strings.Contains(err.Error(), "failed to fetch issues") } Try / catch
issues, err := client.FetchIssues(ctx, filter)
if err != nil {
switch {
case strings.Contains(err.Error(), "status 401"): refreshToken()
case strings.Contains(err.Error(), "transient error"), strings.Contains(err.Error(), "max retries"): scheduleRetry()
default: return err
}
} Prevention
- Always unwrap/log the full error chain - this wrapper hides the real cause in its message suffix
- Health-check the token and project path before each sync run
- Use incremental filters (updated_since) to reduce load and rate-limit pressure
- Wrap with your own context timeout so fetches can't hang past the job deadline
When it happens
Trigger: Any failure of the underlying GET to c.issuesBasePath(): invalid token (401), bad project path (404), rate limit exhausted across retries (429), network outage, or malformed base URL making request creation fail.
Common situations: CI jobs with stale tokens; misconfigured GitLab project/group path; syncing during a GitLab outage; hitting secondary rate limits with large pagination loops; base URL missing scheme.
Related errors
- API error: %s (status %d)
- pagination limit exceeded: stopped after %d pages
- GraphQL error: %s
- failed to fetch issues: %w
- GitLab epic milestone sync: %v
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/307394b5ab9e1b0d.
Report an issue: GitHub.