gastownhall/beads · error
parse issue response: %w
Error message
parse issue response: %w
What it means
GetIssue wraps json.Unmarshal errors for the GET /issue/{key} response. The request succeeded but the body is not valid JSON or doesn't match the Issue struct (id, key, self, fields). Thrown also from CreateIssue's hydration step. Typically indicates an HTML/error body returned with a success status.
Source
Thrown at internal/jira/client.go:246
}
nextPageToken = result.NextPageToken
}
return allIssues, nil
}
// GetIssue fetches a single Jira issue by key (e.g., "PROJ-123").
func (c *Client) GetIssue(ctx context.Context, key string) (*Issue, error) {
apiURL := fmt.Sprintf("%s/issue/%s?fields=%s", c.apiBase(), url.PathEscape(key), searchFields)
body, err := c.doRequest(ctx, "GET", apiURL, nil)
if err != nil {
return nil, fmt.Errorf("get issue %s: %w", key, err)
}
var issue Issue
if err := json.Unmarshal(body, &issue); err != nil {
return nil, fmt.Errorf("parse issue response: %w", err)
}
return &issue, nil
}
// CreateIssue creates a new issue in Jira.
// fields should include "project", "summary", "issuetype", and optionally other fields.
func (c *Client) CreateIssue(ctx context.Context, fields map[string]interface{}) (*Issue, error) {
payload := map[string]interface{}{"fields": fields}
data, err := json.Marshal(payload)
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 {View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the raw body (log a prefix of it) to identify HTML vs JSON vs unexpected schema.
- Confirm the base URL and APIVersion so requests hit /rest/api/N not a UI route.
- Test the endpoint with curl and the same auth to bypass/identify interception layers.
- Update struct tags if your Jira version changed the issue response shape.
Example fix
// before
var issue Issue
if err := json.Unmarshal(body, &issue); err != nil {
return nil, fmt.Errorf("parse issue response: %w", err)
}
// after: surface body for diagnosis
if err := json.Unmarshal(body, &issue); err != nil {
return nil, fmt.Errorf("parse issue response: %w (body: %.200s)", err, string(body))
} Defensive patterns
Strategy: type-guard
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 issueLooksValid(i *jira.Issue) bool {
return i != nil && i.ID != "" && i.Key != ""
} Try / catch
issue, err := client.GetIssue(ctx, key)
if err != nil && strings.Contains(err.Error(), "parse issue response") {
// non-JSON 2xx body: check proxy/SSO interception and base URL
log.Printf("issue fetch for %s returned unparseable body; bypass gateway and retry", key)
return
} Prevention
- Route REST calls directly to Jira, bypassing gateways that inject HTML
- Re-verify Issue struct tags after Jira platform upgrades
- Confirm base URL resolves to the REST API, not a login page
- Alert on repeated parse failures — they usually indicate infra, not code
When it happens
Trigger: GET /issue/{key} returns 200 with a non-JSON body: proxy/SSO HTML page, an API-gateway error envelope, or a response shape incompatible with the Issue struct.
Common situations: Corporate proxies or captive portals rewriting responses; wrong base URL routed to a login page; Jira plugins modifying issue payloads; mismatched API version returning unexpected schema.
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
- parse Jira response: %w
- parse search response: %w
- parse create response: %w
- marshal create request: %w
- ExternalDoltConfig: must set Socket or (Host, Port)
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/d76118842fe67b52.
Report an issue: GitHub.