gastownhall/beads · error
failed to parse milestones response: %w
Error message
failed to parse milestones response: %w
What it means
After FetchMilestones receives a successful HTTP response from /projects/:id/milestones, the body is unmarshaled into []Milestone. This error wraps the json.Unmarshal failure when the body is not a valid JSON array of milestones or contains values that conflict with the Milestone struct. The HTTP call itself succeeded, so the payload is malformed or unexpected.
Source
Thrown at internal/gitlab/client.go:430
// 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)
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)
}
View on GitHub (pinned to 71377f2769)
Solutions
- Log the raw body and the wrapped json.SyntaxError/*json.UnmarshalTypeError to see what the server actually returned
- Verify the base URL points at the GitLab API host, not the web UI or an SSO redirect
- Align the Milestone struct json tags/types with your GitLab version's payload
- Retry if a transient gateway error could have truncated the response
Example fix
// before
ms, err := client.FetchMilestones(ctx, "")
if err != nil { return nil, err }
// after
ms, err := client.FetchMilestones(ctx, "")
if err != nil {
var te *json.UnmarshalTypeError
if errors.As(err, &te) {
log.Printf("milestone payload mismatch at %s: %v", te.Field, te.Error())
}
return nil, err
} Defensive patterns
Strategy: type-guard
Validate before calling
// Ensure API base URL targets the GitLab API, not a proxy/web UI
u, err := url.Parse(gitlabHost)
if err != nil || u.Host == "" {
return fmt.Errorf("invalid gitlab host %q", gitlabHost)
} Type guard
func isMalformedResponse(err error) bool {
var se *json.SyntaxError
var te *json.UnmarshalTypeError
return errors.As(err, &se) || errors.As(err, &te)
} Try / catch
ms, err := client.FetchMilestones(ctx, state)
if err != nil {
if isMalformedResponse(err) {
return nil, fmt.Errorf("gitlab returned malformed milestones payload: %w", err)
}
return nil, err
} Prevention
- Exclude the GitLab API host from SSO/proxy HTML injection
- Log the raw body on parse failure to identify proxies or truncation
- Keep the Milestone struct's json tags matched to your GitLab version
- Monitor GitLab release notes for milestones API payload changes
When it happens
Trigger: Calling Client.FetchMilestones when a proxy/SSO returns HTML, the response is empty/truncated, or a GitLab version emits milestone fields whose JSON types clash with the Milestone struct (e.g. null vs string).
Common situations: Reverse proxies injecting HTML error pages; self-managed GitLab with a divergent milestones payload; wrong base URL landing on a non-API route; encoding/charset issues from an intermediary.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to parse milestone response: %w
- failed to parse issue links response: %w
- failed to parse issue response: %w
- failed to parse work item states value: %w
- failed to marshal request body: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e98bf0f163c9254f.
Report an issue: GitHub.