{"record":{"id":"d19785dba0f3cee4","repo":"gastownhall/beads","slug":"search-issues-w","errorCode":null,"errorMessage":"search issues: %w","messagePattern":"search issues: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/jira/client.go","lineNumber":206,"sourceCode":"\t\t\t\"fields\":     {searchFields},\n\t\t\t\"maxResults\": {fmt.Sprintf(\"%d\", maxResults)},\n\t\t}\n\t\tif useV2Pagination {\n\t\t\tparams.Set(\"startAt\", fmt.Sprintf(\"%d\", startAt))\n\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","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/jira/client.go#L188-L224","documentation":"SearchIssues wraps errors from doRequest (the HTTP GET to /search/jql or /search). It means the transport layer failed before a body could be parsed: non-2xx status, connection error, timeout, or canceled context. The library wraps it with the search context so callers know the JQL query itself failed at the HTTP level.","triggerScenarios":"Any SearchIssues call where the HTTP request fails: 401/403 (bad API token), 404 (wrong API version path), 400 (malformed JQL), 429 (rate limit), network outage, or 30s client timeout on huge queries.","commonSituations":"Expired or wrong Atlassian API token; email vs username confusion in basic auth; Jira Server lacking the v3 /search/jql endpoint; JQL syntax errors rejected with 400; rate limiting during bulk syncs.","solutions":["Check wrapped error for HTTP status: fix auth (401/403) by regenerating the API token and using account email.","If 404, set c.APIVersion appropriately (\"2\" for older Jira Server which lacks /search/jql).","If 400, validate JQL syntax in the Jira UI search bar first.","If 429 or timeout, back off and retry with a smaller maxResults or narrower JQL."],"exampleFix":"// before: opaque failure\nissues, err := client.SearchIssues(ctx, jql)\n// after: retry transient failures\nvar issues []jira.Issue\nerr := retry(3, func() error {\n    var e error\n    issues, e = client.SearchIssues(ctx, jql)\n    return e\n})","handlingStrategy":"retry","validationCode":"// Preflight auth and endpoint before searching:\nresp, err := http.Get(client.URL + \"/rest/api/3/myself\") // expect 200 with valid basic auth\nif err != nil || resp.StatusCode != 200 {\n    return fmt.Errorf(\"Jira preflight failed: status %d\", resp.StatusCode)\n}","typeGuard":"func isRateLimit(err error) bool {\n    return err != nil && (strings.Contains(err.Error(), \"429\") || strings.Contains(err.Error(), \"rate\"))\n}","tryCatchPattern":"var issues []jira.Issue\nbackoff := time.Second\nfor attempt := 0; attempt < 3; attempt++ {\n    issues, err = client.SearchIssues(ctx, jql)\n    if err == nil { break }\n    if isRateLimit(err) || errors.Is(err, context.DeadlineExceeded) {\n        time.Sleep(backoff); backoff *= 2; continue\n    }\n    return err // non-transient: bad JQL, auth\n}","preventionTips":["Validate JQL in the Jira UI before hard-coding it","Rotate API tokens before expiry; use account email for Cloud auth","Match APIVersion to deployment (Server often needs \"2\")","Add backoff for 429s and keep maxResults modest during bulk syncs"],"tags":["jira","http","network","jql","go"],"backgroundTag":"http-request-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}