{"record":{"id":"e1bbe1f163c2ff20","repo":"wtfutil/wtf","slug":"failed-to-parse-jql-search-response-v","errorCode":null,"errorMessage":"failed to parse JQL search response: %v","messagePattern":"failed to parse JQL search response: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/jira/client.go","lineNumber":234,"sourceCode":"\n\tjqlURL := fmt.Sprintf(\"/rest/api/3/search/jql?%s\", v.Encode())\n\n\tresp, err := widget.jiraRequest(jqlURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Parse the JQL response which contains issue IDs\n\ttype JQLSearchResult struct {\n\t\tIssues []struct {\n\t\t\tID string `json:\"id\"`\n\t\t} `json:\"issues\"`\n\t}\n\n\tjqlResult := &JQLSearchResult{}\n\terr = utils.ParseJSON(jqlResult, bytes.NewReader(resp))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse JQL search response: %v\", err)\n\t}\n\n\tif len(jqlResult.Issues) == 0 {\n\t\t// Return empty result if no issues found\n\t\treturn &SearchResult{Issues: []Issue{}}, nil\n\t}\n\n\t// Now get full issue details for each ID\n\tsearchResult := &SearchResult{Issues: []Issue{}}\n\n\tfor i, issue := range jqlResult.Issues {\n\t\t// Limit to prevent too many API calls\n\t\tif i >= 20 {\n\t\t\tbreak\n\t\t}\n\n\t\tfullIssue, err := widget.getIssueByID(issue.ID)\n\t\tif err != nil {","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/wtfutil/wtf/blob/bb838c1ccb0f0f3223690df44afdec663d622881/modules/jira/client.go#L216-L252","documentation":"searchWithNewAPI fails with this error when utils.ParseJSON cannot decode the /rest/api/3/search/jql response body into the JQLSearchResult struct. The HTTP call succeeded but the body doesn't match the expected JSON shape (issues array, pagination fields, etc.).","triggerScenarios":"Jira returns 200 with a body whose JSON keys/types differ from JQLSearchResult (API shape change), or an error payload/HTML page arrives with a 200-like status, or a truncated/garbled body from a proxy.","commonSituations":"Atlassian changing field names/types in search/jql responses, an API gateway or proxy returning an HTML error page, a stale fork of the client missing new response fields, gzip/encoding issues from intermediaries.","solutions":["Log the raw response body to see the actual JSON returned","Compare the JQLSearchResult struct tags with the current /rest/api/3/search/jql response schema from Atlassian docs","Bypass/update any proxy that might rewrite the response","Update the client (or struct tags) to the current API response format"],"exampleFix":"// before\nissues []struct {\n    Key string `json:\"key\"`\n}\n// after (match current API payload)\nissues []struct {\n    Key    string `json:\"key\"`\n    Fields struct {\n        Summary string `json:\"summary\"`\n    } `json:\"fields\"`\n} `json:\"issues\"`","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func looksLikeJQLSearchResponse(body []byte) bool {\n    var probe struct {\n        Issues []json.RawMessage `json:\"issues\"`\n    }\n    return json.Unmarshal(body, &probe) == nil\n}","tryCatchPattern":"result, err := widget.IssuesFor(ctx, username, jql)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to parse JQL search response\") {\n        log.Printf(\"response schema drift or proxy interference: %v\", err)\n    }\n    return err\n}","preventionTips":["Keep JQLSearchResult json tags aligned with the current Atlassian schema","Snapshot real API responses in fixtures and parse them in tests","Ensure proxies/gateways don't inject HTML error pages on 200","Handle null fields defensively in the struct (pointers/omitempty)"],"tags":["json","parsing","jira","go"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"bb838c1ccb0f0f3223690df44afdec663d622881","analyzedAt":"2026-09-03T17:02:45.030Z","contentChangedAt":"2026-09-03T17:02:45.030Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}