{"record":{"id":"dfef77d0d50d0fbc","repo":"gastownhall/beads","slug":"parse-jira-response-w","errorCode":null,"errorMessage":"parse Jira response: %w","messagePattern":"parse Jira response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/jira/client.go","lineNumber":151,"sourceCode":"// FetchIssueTimestamp fetches the updated timestamp for a single Jira issue.\nfunc (c *Client) FetchIssueTimestamp(ctx context.Context, jiraKey string) (time.Time, error) {\n\tvar zero time.Time\n\n\tapiURL := fmt.Sprintf(\"%s/issue/%s?fields=updated\", c.apiBase(), url.PathEscape(jiraKey))\n\n\tbody, err := c.doRequest(ctx, \"GET\", apiURL, nil)\n\tif err != nil {\n\t\treturn zero, fmt.Errorf(\"fetch issue %s: %w\", jiraKey, err)\n\t}\n\n\tvar result struct {\n\t\tFields struct {\n\t\t\tUpdated string `json:\"updated\"`\n\t\t} `json:\"fields\"`\n\t}\n\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn zero, fmt.Errorf(\"parse Jira response: %w\", err)\n\t}\n\n\tupdated, err := ParseTimestamp(result.Fields.Updated)\n\tif err != nil {\n\t\treturn zero, fmt.Errorf(\"parse Jira timestamp: %w\", err)\n\t}\n\n\treturn updated, nil\n}\n\n// searchFields is the default set of fields to request in search/get queries.\nconst searchFields = \"summary,description,status,priority,issuetype,project,assignee,labels,created,updated,resolution\"\n\n// SearchIssues queries Jira using JQL and returns all matching issues, handling pagination.\nfunc (c *Client) SearchIssues(ctx context.Context, jql string) ([]Issue, error) {\n\tvar allIssues []Issue\n\tstartAt := 0\n\tnextPageToken := \"\"","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/jira/client.go#L133-L169","documentation":"FetchIssueTimestamp wraps errors from json.Unmarshal of the GET /issue/{key}?fields=updated response body. It means the HTTP request succeeded but the body was not valid JSON or did not match the expected {\"fields\":{\"updated\":\"...\"}} shape. The library throws it because a truncated, HTML, or malformed response cannot be interpreted as a Jira issue payload.","triggerScenarios":"Calling FetchIssueTimestamp when the Jira server returns a non-JSON body (e.g., an HTML login/SSO page, a proxy error page, or a truncated response) despite a 2xx status.","commonSituations":"Corporate proxies or SSO interceptors returning HTML with 200 OK; misconfigured base URL pointing at a non-REST endpoint; custom Jira plugins or gateways altering response bodies; network truncation mid-body.","solutions":["Verify the Client.URL points to the Jira instance root (e.g. https://yourorg.atlassian.net) so the API base resolves to /rest/api/3.","Log or print the raw body on this error to see what the server actually returned (html vs json).","Check for SSO/proxy interception: test the same URL with curl using the same credentials.","Confirm APIVersion (2 vs 3) matches your deployment; wrong-version endpoints may return unexpected payloads."],"exampleFix":"// before: err swallowed, opaque failure\nif err := json.Unmarshal(body, &result); err != nil {\n    return zero, fmt.Errorf(\"parse Jira response: %w\", err)\n}\n// after: capture body snippet for diagnosis\nif err := json.Unmarshal(body, &result); err != nil {\n    return zero, fmt.Errorf(\"parse Jira response: %w (body: %.200s)\", err, string(body))\n}","handlingStrategy":"try-catch","validationCode":"// Go has no pre-call validation for server response bodies; verify reachability/shape first:\nresp, err := http.Get(client.URL + \"/rest/api/3/myself\")\nif err != nil || !strings.Contains(resp.Header.Get(\"Content-Type\"), \"application/json\") {\n    // base URL or proxy misconfigured\n}","typeGuard":"func isJSONContentType(h http.Header) bool {\n    return strings.Contains(h.Get(\"Content-Type\"), \"application/json\")\n}","tryCatchPattern":"ts, err := client.FetchIssueTimestamp(ctx, key)\nif err != nil {\n    var parseErr *json.UnmarshalTypeError\n    if errors.As(err, &parseErr) || strings.Contains(err.Error(), \"parse Jira response\") {\n        // non-JSON body: log, skip this key, alert on proxy/auth config\n        return\n    }\n    return err\n}","preventionTips":["Point Client.URL at the instance root, never a UI or proxy path","Check Content-Type of responses when debugging","Watch for SSO/proxy HTML injection on API routes","Pin APIVersion explicitly rather than relying on defaults"],"tags":["jira","json","http-response","go"],"backgroundTag":"invalid-json-response","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}