gastownhall/beads · error

parse search response: %w

Error message

parse search response: %w

What it means

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.

Source

Thrown at internal/jira/client.go:211

		} else if nextPageToken != "" {
			params.Set("nextPageToken", nextPageToken)
		}

		// v3 uses /search/jql; v2 uses /search (both accept jql as a query param)
		searchPath := "search/jql"
		if useV2Pagination {
			searchPath = "search"
		}
		apiURL := fmt.Sprintf("%s/%s?%s", c.apiBase(), searchPath, params.Encode())

		body, err := c.doRequest(ctx, "GET", apiURL, nil)
		if err != nil {
			return nil, fmt.Errorf("search issues: %w", err)
		}

		var result SearchResult
		if err := json.Unmarshal(body, &result); err != nil {
			return nil, fmt.Errorf("parse search response: %w", err)
		}

		allIssues = append(allIssues, result.Issues...)

		if len(result.Issues) == 0 {
			break
		}
		if useV2Pagination {
			if startAt+len(result.Issues) >= result.Total {
				break
			}
			startAt += len(result.Issues)
			continue
		}
		if result.IsLast || result.NextPageToken == "" {
			break
		}
		nextPageToken = result.NextPageToken

View on GitHub (pinned to 71377f2769)

Solutions

  1. Dump the raw body on failure to see whether it's HTML, an error envelope, or malformed JSON.
  2. Verify APIVersion matches the deployment (v2 vs v3 search endpoints return compatible but distinct payloads).
  3. Test the identical URL with curl and valid credentials to rule out proxy/SSO rewriting.
  4. Update the client if your Jira version changed its search response schema.

Example fix

// before
var result SearchResult
if err := json.Unmarshal(body, &result); err != nil {
    return nil, fmt.Errorf("parse search response: %w", err)
}
// after: include body context
if err := json.Unmarshal(body, &result); err != nil {
    return nil, fmt.Errorf("parse search response: %w (body: %.200s)", err, string(body))
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Detect non-JSON responses at the transport level before parse failures propagate:
func looksLikeHTML(b []byte) bool {
    s := bytes.TrimLeft(b, " \t\r\n")
    return bytes.HasPrefix(s, []byte("<"))
}

Type guard

func isJSONBody(b []byte) bool {
    s := bytes.TrimLeft(b, " \t\r\n")
    return len(s) > 0 && (s[0] == '{' || s[0] == '[')
}

Try / catch

issues, err := client.SearchIssues(ctx, jql)
if err != nil && strings.Contains(err.Error(), "parse search response") {
    // body was HTML or wrong schema: dump body, check proxy/SSO, verify APIVersion
    log.Printf("search returned non-JSON body; check gateway config")
    return
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/e8302da71afba6ea. Report an issue: GitHub.