gastownhall/beads · error
failed to parse milestone response: %w
Error message
failed to parse milestone response: %w
What it means
After FetchMilestoneByIID gets a successful response from /projects/:id/milestones?iids[]=N, the body is unmarshaled into []Milestone. This error wraps the json.Unmarshal failure when the body is not a valid JSON array matching the Milestone struct. Note FetchMilestoneByIID itself returns (nil, nil) for an empty array, so this error is strictly about malformed payloads, not 'not found'.
Source
Thrown at internal/gitlab/client.go:451
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,
"description": description,
}
urlStr := c.buildURL("/projects/"+c.projectPath()+"/milestones", nil)
respBody, _, err := c.doRequest(ctx, http.MethodPost, urlStr, body)View on GitHub (pinned to 71377f2769)
Solutions
- Log the raw response body and the wrapped json error (SyntaxError vs UnmarshalTypeError) to identify the mismatch
- Verify the base URL targets the GitLab API host, not a web UI or SSO redirect
- Update the Milestone struct json tags/types to match your GitLab version's actual payload
- Retry if a transient gateway error could have truncated the body
Example fix
// before
m, err := client.FetchMilestoneByIID(ctx, iid)
// after
m, err := client.FetchMilestoneByIID(ctx, iid)
if err != nil {
var se *json.SyntaxError
if errors.As(err, &se) {
log.Printf("non-JSON milestone payload (offset %d): likely HTML from proxy", se.Offset)
}
return err
} Defensive patterns
Strategy: type-guard
Validate before calling
// Validate API host configuration to avoid proxy-injected HTML
if u, err := url.Parse(gitlabHost); err != nil || u.Scheme == "" || u.Host == "" {
return fmt.Errorf("misconfigured gitlab host: %q", gitlabHost)
} Type guard
func isPayloadError(err error) bool {
var se *json.SyntaxError
var te *json.UnmarshalTypeError
return errors.As(err, &se) || errors.As(err, &te)
}
// Also narrow the nil result:
func milestoneFound(m *Milestone) bool { return m != nil && m.ID != 0 } Try / catch
m, err := client.FetchMilestoneByIID(ctx, iid)
if err != nil {
if isPayloadError(err) {
return fmt.Errorf("malformed milestone payload: %w", err)
}
return err
}
if !milestoneFound(m) {
return nil // legitimately not found — function returns nil,nil
} Prevention
- Remember FetchMilestoneByIID returns (nil, nil) when not found — always nil-check the result
- Keep the Milestone struct in sync with your GitLab version
- Bypass proxies/SSO for API traffic to avoid HTML responses
- Log raw bodies on parse errors to diagnose gateway truncation
When it happens
Trigger: Calling Client.FetchMilestoneByIID when a proxy/SSO injects HTML into the response, the body is empty/truncated, or a GitLab version emits milestone field types that conflict with the Milestone struct (e.g. number-vs-string IDs).
Common situations: Corporate proxies or error pages returned with 200 responses; self-managed GitLab versions with divergent milestone schemas; wrong base URL hitting a non-API route; truncated responses from flaky gateways.
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 milestones 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/17de3e3727c681f7.
Report an issue: GitHub.