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

  1. Log the raw response body and wrapped Unmarshal error to find the mismatching field
  2. Update the library/Issue struct to match the GitLab server version
  3. Bypass or fix proxies/middleware that rewrite API responses
  4. 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

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

Related errors


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