gastownhall/beads · error

failed to parse issues response: %w

Error message

failed to parse issues response: %w

What it means

FetchIssues failed to JSON-decode a GitLab issues list response into []Issue. The library throws this when the HTTP response body is not the expected JSON array of issue objects, so the pagination loop aborts with the underlying json.Unmarshal error wrapped.

Source

Thrown at internal/gitlab/client.go:259

		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 {
			return nil, fmt.Errorf("pagination limit exceeded: stopped after %d pages", MaxPages)
		}
	}

	return filterByProject(allIssues, filter), nil

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check baseURL/token config: curl the same URL and confirm the body is a JSON array of issues
  2. Inspect the wrapped %w error for the exact Unmarshal mismatch (field/type) and compare against the Issue struct for your GitLab version
  3. Upgrade/downgrade the library to match your GitLab server version
  4. If behind a proxy, bypass it or fix it so API responses pass through untouched

Example fix

// before
issues, err := client.FetchIssues(ctx, "opened")
if err != nil { panic(err) }
// after
issues, err := client.FetchIssues(ctx, "opened")
if err != nil {
	log.Fatalf("gitlab fetch failed (check baseURL/token/GitLab version): %v", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

resp, _ := http.Get(baseURL + "/api/v4/projects/" + url.PathEscape(project) + "/issues?per_page=1")
body, _ := io.ReadAll(resp.Body)
if !json.Valid(body) || body[0] != '[' {
	return fmt.Errorf("endpoint does not return a JSON array of issues")
}

Type guard

func isJSONParseError(err error) bool {
	var jsonErr *json.UnmarshalTypeError
	return errors.As(err, &jsonErr) || strings.Contains(err.Error(), "failed to parse issues response")
}

Try / catch

issues, err := client.FetchIssues(ctx, "opened")
if err != nil {
	var synErr *net.OpError
	if errors.Is(err, context.Canceled) { return err }
	log.Printf("gitlab response unparseable (check baseURL/proxy/GitLab version): %v", err)
	return nil // or fall back to cached issues
}

Prevention

When it happens

Trigger: GET /projects/:id/issues returns a body that does not unmarshal into []Issue — e.g. a proxy/login page returning HTML, a JSON error object instead of an array, or a GitLab schema change adding a field with an incompatible type.

Common situations: Wrong base URL pointing at a non-GitLab host (HTML 200 response), an auth proxy or SSO redirect page, GitLab version with changed field types (e.g. dates or IDs changing shape), or a reverse proxy returning an error page with 200.

Understand the failure class

Related errors


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