gastownhall/beads · error

parse create response

Error message

parse create response: %w

What it means

CreateIssue wraps json.Unmarshal errors for the POST /issue response. The issue was likely created (2xx) but the body — expected to contain id, key, self — could not be decoded, usually because it's HTML or an unexpected envelope. The library throws it because it needs created.Key to fetch the full issue.

Solutions

  1. Inspect the raw body to confirm whether it's an HTML page or a differently-shaped JSON success object.
  2. Before retrying CreateIssue, search Jira by summary/project to avoid creating duplicates.
  3. Remove proxy/SSO interception for API routes or bypass the gateway for REST calls.
  4. Parse defensively: attempt to extract "key" even if the full struct doesn't match.

Example fix

// before
if err := json.Unmarshal(body, &created); err != nil {
    return nil, fmt.Errorf("parse create response: %w", err)
}
// after: log body and warn that the issue may exist
if err := json.Unmarshal(body, &created); err != nil {
    return nil, fmt.Errorf("parse create response: %w (issue may have been created; body: %.200s)", err, string(body))
}
Defensive patterns

Strategy: try-catch

Validate before calling

func isJSONBody(b []byte) bool {
    s := bytes.TrimLeft(b, " \t\r\n")
    return len(s) > 0 && (s[0] == '{' || s[0] == '[')
}

Type guard

func createResponseUsable(b []byte) bool {
    var c struct{ Key string `json:"key"` }
    return json.Unmarshal(b, &c) == nil && c.Key != ""
}

Try / catch

issue, err := client.CreateIssue(ctx, fields)
if err != nil && strings.Contains(err.Error(), "parse create response") {
    // Issue was likely created; DO NOT blindly retry (duplicate risk).
    // Search by summary to locate or confirm the created issue:
    found, serr := client.SearchIssues(ctx, fmt.Sprintf("project = %s AND summary ~ %q ORDER BY created DESC", projectKey, summary))
    if serr == nil && len(found) > 0 {
        issue = &found[0]
        err = nil
    }
}

Prevention

When it happens

Trigger: POST /issue returns 2xx with a non-JSON or unexpected body: proxy/SSO HTML injected after success, gateway response wrappers, or an API version whose create response schema differs.

Common situations: Corporate proxies rewriting successful responses; custom Jira apps altering create responses; mistyped base URL that happens to accept POST but returns HTML; truncated response bodies on flaky connections. Danger: the issue exists but the caller never learns its key, risking duplicates on retry.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at internal/jira/client.go:275

	if err != nil {
		return nil, fmt.Errorf("marshal create request: %w", err)
	}

	apiURL := fmt.Sprintf("%s/issue", c.apiBase())

	body, err := c.doRequest(ctx, "POST", apiURL, data)
	if err != nil {
		return nil, fmt.Errorf("create issue: %w", err)
	}

	// Create response only returns id, key, self. Fetch the full issue.
	var created struct {
		ID   string `json:"id"`
		Key  string `json:"key"`
		Self string `json:"self"`
	}
	if err := json.Unmarshal(body, &created); err != nil {
		return nil, fmt.Errorf("parse create response: %w", err)
	}

	return c.GetIssue(ctx, created.Key)
}

// UpdateIssue updates an existing Jira issue by key.
func (c *Client) UpdateIssue(ctx context.Context, key string, fields map[string]interface{}) error {
	payload := map[string]interface{}{"fields": fields}
	data, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("marshal update request: %w", err)
	}

	apiURL := fmt.Sprintf("%s/issue/%s", c.apiBase(), url.PathEscape(key))

	_, err = c.doRequest(ctx, "PUT", apiURL, data)
	if err != nil {
		return fmt.Errorf("update issue %s: %w", key, err)

View on GitHub (pinned to 71377f2769)