gastownhall/beads · error

failed to read response (attempt %d/%d): %w

Error message

failed to read response (attempt %d/%d): %w

What it means

This error wraps an underlying I/O failure that occurred while reading the Jira HTTP response body in doRequest. The client reads up to MaxResponseSize bytes via io.ReadAll on a LimitReader; any read failure (connection reset mid-body, TLS error, premature EOF) is wrapped with the attempt counter (of MaxRetries+1) and the original error. The request is then retried until attempts are exhausted, after which the last wrapped error surfaces via 'max retries exceeded'.

Source

Thrown at internal/jira/client.go:381

		}

		c.setAuth(req)
		req.Header.Set("Accept", "application/json")
		req.Header.Set("User-Agent", "bd-jira-sync/1.0")
		if body != nil {
			req.Header.Set("Content-Type", "application/json")
		}

		resp, err := c.HTTPClient.Do(req)
		if err != nil {
			lastErr = fmt.Errorf("request failed (attempt %d/%d): %w", attempt+1, MaxRetries+1, err)
			continue
		}

		respBody, err := io.ReadAll(io.LimitReader(resp.Body, MaxResponseSize))
		_ = resp.Body.Close()
		if err != nil {
			lastErr = fmt.Errorf("failed to read response (attempt %d/%d): %w", attempt+1, MaxRetries+1, err)
			continue
		}

		// PUT returns 204 No Content on success
		if resp.StatusCode == http.StatusNoContent {
			return nil, nil
		}

		if resp.StatusCode >= 200 && resp.StatusCode < 300 {
			return respBody, nil
		}

		// Permanent failures — no retry.
		switch resp.StatusCode {
		case http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound:
			return nil, fmt.Errorf("jira API returned %d: %s", resp.StatusCode, string(respBody))
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry the operation — the client already retries internally; if it fails all attempts, check network stability between client and the Jira host.
  2. Inspect the wrapped cause (%w) at the end of the message to identify the transport-level problem (e.g. 'connection reset by peer', 'unexpected EOF').
  3. Check for proxies/firewalls/load balancers between the client and Jira that terminate connections; raise their idle timeouts.
  4. If self-hosted Jira, check server logs and reverse-proxy (nginx/ALB) timeout settings.
  5. Verify TLS certificates and that the Jira URL scheme (http/https) is correct.

Example fix

// before: seeing 'failed to read response (attempt 3/4): unexpected EOF' with no insight
resp, _ := client.doRequest(ctx, req)
// after: log the unwrapped cause to diagnose the transport failure
if err != nil {
    var unwrapped error = errors.Unwrap(err)
    log.Printf("jira read failed, cause: %v", unwrapped)
}
Defensive patterns

Strategy: retry

Validate before calling

// Verify reachability before issuing real calls
req, _ := http.NewRequestWithContext(ctx, "GET", jiraURL+"/rest/api/3/myself", nil)
if _, err := http.DefaultClient.Do(req); err != nil {
    return fmt.Errorf("jira host unreachable, check network/proxy: %w", err)
}

Try / catch

if err != nil {
    if strings.Contains(err.Error(), "failed to read response") || strings.Contains(err.Error(), "max retries") {
        // transient transport issue: back off and retry later
        time.Sleep(backoff)
        return retry(op)
    }
    return err
}

Prevention

When it happens

Trigger: io.ReadAll(resp.Body) returns a non-nil err during any retry attempt of doRequest — e.g. the server closes the connection mid-body, a proxy drops the stream, or a TLS read fails.

Common situations: Corporate proxies or load balancers with short idle timeouts killing long responses; flaky VPN/network; Jira instance (especially self-hosted) restarting under load; response larger than expected causing client/server disagreement.

Related errors


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