{"record":{"id":"e8302da71afba6ea","repo":"gastownhall/beads","slug":"parse-search-response-w","errorCode":null,"errorMessage":"parse search response: %w","messagePattern":"parse search response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/jira/client.go","lineNumber":211,"sourceCode":"\t\t} else if nextPageToken != \"\" {\n\t\t\tparams.Set(\"nextPageToken\", nextPageToken)\n\t\t}\n\n\t\t// v3 uses /search/jql; v2 uses /search (both accept jql as a query param)\n\t\tsearchPath := \"search/jql\"\n\t\tif useV2Pagination {\n\t\t\tsearchPath = \"search\"\n\t\t}\n\t\tapiURL := fmt.Sprintf(\"%s/%s?%s\", c.apiBase(), searchPath, params.Encode())\n\n\t\tbody, err := c.doRequest(ctx, \"GET\", apiURL, nil)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"search issues: %w\", err)\n\t\t}\n\n\t\tvar result SearchResult\n\t\tif err := json.Unmarshal(body, &result); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"parse search response: %w\", err)\n\t\t}\n\n\t\tallIssues = append(allIssues, result.Issues...)\n\n\t\tif len(result.Issues) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tif useV2Pagination {\n\t\t\tif startAt+len(result.Issues) >= result.Total {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tstartAt += len(result.Issues)\n\t\t\tcontinue\n\t\t}\n\t\tif result.IsLast || result.NextPageToken == \"\" {\n\t\t\tbreak\n\t\t}\n\t\tnextPageToken = result.NextPageToken","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/jira/client.go#L193-L229","documentation":"SearchIssues wraps errors from json.Unmarshal of a search page's body. The HTTP call succeeded but the payload is not valid JSON or does not match SearchResult's shape (issues, total, nextPageToken, isLast). The library throws it because pagination logic cannot proceed with an uninterpretable page.","triggerScenarios":"A search page response that is HTML (proxy/SSO interstitial), an error object returned with 2xx, or a body whose types clash with SearchResult fields (e.g. issues as object instead of array).","commonSituations":"API-version mismatch returning a differently-shaped search payload; gateway/auth middleware returning HTML with 200; custom Jira apps altering search responses; truncated bodies on flaky networks.","solutions":["Dump the raw body on failure to see whether it's HTML, an error envelope, or malformed JSON.","Verify APIVersion matches the deployment (v2 vs v3 search endpoints return compatible but distinct payloads).","Test the identical URL with curl and valid credentials to rule out proxy/SSO rewriting.","Update the client if your Jira version changed its search response schema."],"exampleFix":"// before\nvar result SearchResult\nif err := json.Unmarshal(body, &result); err != nil {\n    return nil, fmt.Errorf(\"parse search response: %w\", err)\n}\n// after: include body context\nif err := json.Unmarshal(body, &result); err != nil {\n    return nil, fmt.Errorf(\"parse search response: %w (body: %.200s)\", err, string(body))\n}","handlingStrategy":"type-guard","validationCode":"// Detect non-JSON responses at the transport level before parse failures propagate:\nfunc looksLikeHTML(b []byte) bool {\n    s := bytes.TrimLeft(b, \" \\t\\r\\n\")\n    return bytes.HasPrefix(s, []byte(\"<\"))\n}","typeGuard":"func isJSONBody(b []byte) bool {\n    s := bytes.TrimLeft(b, \" \\t\\r\\n\")\n    return len(s) > 0 && (s[0] == '{' || s[0] == '[')\n}","tryCatchPattern":"issues, err := client.SearchIssues(ctx, jql)\nif err != nil && strings.Contains(err.Error(), \"parse search response\") {\n    // body was HTML or wrong schema: dump body, check proxy/SSO, verify APIVersion\n    log.Printf(\"search returned non-JSON body; check gateway config\")\n    return\n}","preventionTips":["Exclude API routes from SSO/proxy HTML interstitials","Verify search payload shape after Jira version upgrades","Confirm the search endpoint exists for your APIVersion (v2 /search vs v3 /search/jql)","Log raw bodies when parse errors recur"],"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"}