gastownhall/beads · error
failed to parse update response: %w
Error message
failed to parse update response: %w
What it means
UpdateIssue received a response body that could not be decoded into a single Issue object after a successful PUT. The body was HTML, an error envelope, or incompatible with the Issue struct.
Source
Thrown at internal/gitlab/client.go:374
var issue Issue
if err := json.Unmarshal(respBody, &issue); err != nil {
return nil, fmt.Errorf("failed to parse create response: %w", err)
}
return &issue, nil
}
// UpdateIssue updates an existing issue in GitLab.
func (c *Client) UpdateIssue(ctx context.Context, iid int, updates map[string]interface{}) (*Issue, error) {
urlStr := c.buildURL("/projects/"+c.projectPath()+"/issues/"+strconv.Itoa(iid), nil)
respBody, _, err := c.doRequest(ctx, http.MethodPut, urlStr, updates)
if err != nil {
return nil, fmt.Errorf("failed to update issue: %w", err)
}
var issue Issue
if err := json.Unmarshal(respBody, &issue); err != nil {
return nil, fmt.Errorf("failed to parse update response: %w", err)
}
return &issue, nil
}
// GetIssueLinks retrieves issue links for the specified issue IID.
func (c *Client) GetIssueLinks(ctx context.Context, iid int) ([]IssueLink, error) {
urlStr := c.buildURL("/projects/"+c.projectPath()+"/issues/"+strconv.Itoa(iid)+"/links", nil)
respBody, _, err := c.doRequest(ctx, http.MethodGet, urlStr, nil)
if err != nil {
return nil, fmt.Errorf("failed to get issue links: %w", err)
}
var links []IssueLink
if err := json.Unmarshal(respBody, &links); err != nil {
return nil, fmt.Errorf("failed to parse issue links response: %w", err)
}
View on GitHub (pinned to 71377f2769)
Solutions
- Log the raw response body and wrapped Unmarshal error to find the mismatching field
- Update the library/Issue struct to match the GitLab server version
- Bypass or fix proxies/middleware that rewrite API responses
- Test the same PUT with curl to confirm the expected issue JSON is returned
Example fix
// before
_, err := client.UpdateIssue(ctx, iid, updates)
// after
issue, err := client.UpdateIssue(ctx, iid, updates)
if err != nil { return fmt.Errorf("update iid %d failed: %w", iid, err) } Defensive patterns
Strategy: try-catch
Validate before calling
req, _ := http.NewRequest(http.MethodPut, url, body)
resp, _ := http.DefaultClient.Do(req)
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") { return fmt.Errorf("non-JSON update response: %s", ct) } Type guard
func isParseUpdateErr(err error) bool {
return strings.Contains(err.Error(), "failed to parse update response")
} Try / catch
issue, err := client.UpdateIssue(ctx, iid, updates)
if err != nil {
if isParseUpdateErr(err) {
// the PUT may have succeeded; re-fetch by IID to confirm instead of retrying the write
return client.FetchIssueByIID(ctx, iid)
}
return err
} Prevention
- Do not blindly retry updates after parse errors — re-fetch to confirm state
- Bypass response-rewriting middleware for API calls
- Keep Issue struct in sync with your GitLab version
- Log the failing body to detect schema drift early
When it happens
Trigger: PUT /projects/:id/issues/:iid returns a non-issue JSON payload (proxy error envelope, GitLab schema drift, or an interceptor returning unexpected JSON) so json.Unmarshal into Issue fails.
Common situations: SSO middleware rewriting API responses, GitLab version mismatch with the library's Issue struct, or a service mesh returning JSON errors with 2xx status.
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 issues response: %w
- failed to parse create response: %w
- failed to parse issue links response: %w
- failed to marshal request body: %w
- request failed (attempt %d/%d): %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e85fc11d5d57d9fc.
Report an issue: GitHub.