gastownhall/beads · error
failed to parse issue response: %w
Error message
failed to parse issue response: %w
What it means
After FetchIssueByIID gets a successful HTTP response from /projects/:id/issues/:iid, the body is unmarshaled into the Issue struct. This error wraps the json.Unmarshal failure when the response body is not valid JSON or does not match the Issue struct shape. The HTTP call succeeded, so the problem is the payload content, not connectivity.
Source
Thrown at internal/gitlab/client.go:406
var links []IssueLink
if err := json.Unmarshal(respBody, &links); err != nil {
return nil, fmt.Errorf("failed to parse issue links response: %w", err)
}
return links, nil
}
// FetchIssueByIID retrieves a single issue by its project-scoped IID.
func (c *Client) FetchIssueByIID(ctx context.Context, iid int) (*Issue, error) {
urlStr := c.buildURL("/projects/"+c.projectPath()+"/issues/"+strconv.Itoa(iid), nil)
respBody, _, err := c.doRequest(ctx, http.MethodGet, urlStr, nil)
if err != nil {
return nil, fmt.Errorf("failed to fetch issue %d: %w", iid, err)
}
var issue Issue
if err := json.Unmarshal(respBody, &issue); err != nil {
return nil, fmt.Errorf("failed to parse issue response: %w", err)
}
return &issue, nil
}
// 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 {View on GitHub (pinned to 71377f2769)
Solutions
- Capture and log the raw response body plus the wrapped *json.UnmarshalTypeError/json.SyntaxError to identify the mismatch
- Confirm the client base URL points at the GitLab API host (not a web UI or SSO redirect)
- Compare the payload against the Issue struct fields; update struct types/json tags to match your GitLab version
- Retry if the body could have been truncated by a transient gateway error
Example fix
// before
issue, err := client.FetchIssueByIID(ctx, iid)
// after
issue, err := client.FetchIssueByIID(ctx, iid)
if err != nil {
var syn *json.SyntaxError
if errors.As(err, &syn) {
log.Printf("invalid JSON at offset %d — body likely HTML/empty", syn.Offset)
}
return err
} Defensive patterns
Strategy: type-guard
Validate before calling
// Confirm base URL is the API host before calling
if !strings.HasSuffix(strings.TrimSuffix(gitlabHost, "/"), "/api") && !strings.Contains(gitlabHost, "/api/") {
// client should build /api/v4 paths itself; a web-UI URL will yield HTML
log.Printf("warning: %s may not be an API host", gitlabHost)
} Type guard
func isJSONParseError(err error) bool {
var se *json.SyntaxError
var te *json.UnmarshalTypeError
return errors.As(err, &se) || errors.As(err, &te)
} Try / catch
issue, err := client.FetchIssueByIID(ctx, iid)
if err != nil {
if isJSONParseError(err) {
// body was HTML/empty — inspect proxy config, not the issue ID
return fmt.Errorf("non-JSON issue payload: %w", err)
}
return err
}
if issue == nil || issue.IID == 0 {
return errors.New("empty issue payload")
} Prevention
- Never front the API host with an SSO redirect for non-interactive clients
- Log raw bodies when unmarshal errors occur to detect HTML error pages
- Keep the Issue struct's json tags in sync with your GitLab release
- Watch for GitLab upgrades that change field types (string vs number)
When it happens
Trigger: Calling Client.FetchIssueByIID when a proxy/SSO layer returns HTML instead of JSON, the body is empty or truncated, or a GitLab API change introduces fields whose types conflict with the Issue struct (e.g. string vs number).
Common situations: Corporate proxies injecting HTML error/login pages; self-hosted GitLab versions with divergent issue payloads; misconfigured base URL hitting a non-API route; gzip/encoding issues stripping the body.
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 issue links response: %w
- failed to parse milestones response: %w
- failed to parse milestone 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/a2b9a7dd3ef7f039.
Report an issue: GitHub.