gastownhall/beads · error
jira API returned %d: %s
Error message
jira API returned %d: %s
What it means
doRequest returns this immediately (no retry) when Jira responds with a status the client classifies as a permanent client failure: 400 Bad Request, 401 Unauthorized, 403 Forbidden, or 404 Not Found. The message embeds the raw response body so the server's own error explanation is visible. Retrying cannot fix these — the request itself or the credentials/URL must change.
Source
Thrown at internal/jira/client.go:397
_ = 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))
}
// Retry on rate-limiting and server errors with exponential backoff.
retriable := resp.StatusCode == http.StatusTooManyRequests ||
resp.StatusCode == http.StatusInternalServerError ||
resp.StatusCode == http.StatusBadGateway ||
resp.StatusCode == http.StatusServiceUnavailable ||
resp.StatusCode == http.StatusGatewayTimeout
if retriable {
delay := RetryDelay * time.Duration(1<<uint(attempt))
useServerDelay := false
// Use Retry-After header if present (no jitter — respect server-mandated delay)
if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
if seconds, parseErr := strconv.Atoi(retryAfter); parseErr == nil {
delay = time.Duration(seconds) * time.Second
useServerDelay = trueView on GitHub (pinned to 71377f2769)
Solutions
- Read the embedded response body in the message — Jira's error JSON usually names the exact problem.
- For 401: regenerate the API token and verify jira.api_token/JIRA_API_TOKEN matches the username/email exactly.
- For 403: check the account's project permissions in Jira admin; verify the account is not deactivated.
- For 404: verify the issue key/project key and that jira.url points at the correct site (e.g. https://yourorg.atlassian.net).
- For 400: fix the request payload or JQL; test the same query in the Jira web UI or REST browser.
Example fix
// before: wrong field name in payload → 400
payload := map[string]any{"fields": map[string]any{"summary": s, "prority": map[string]any{"name": p}}}
// after: correct field name
payload := map[string]any{"fields": map[string]any{"summary": s, "priority": map[string]any{"name": p}}} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight auth + project check before real operations
resp, _ := http.Get(jiraURL + "/rest/api/3/myself")
if resp != nil && (resp.StatusCode == 401 || resp.StatusCode == 403) {
return fmt.Errorf("credentials/permissions invalid before starting (HTTP %d)", resp.StatusCode)
} Try / catch
if err != nil {
if strings.Contains(err.Error(), "jira API returned") {
var code int
fmt.Sscanf(err.Error(), "jira API returned %d", &code)
switch code {
case 401: return refreshCredentials()
case 403: return fmt.Errorf("permission denied on project")
case 404: return fmt.Errorf("issue/project key does not exist")
default: return fmt.Errorf("bad request, inspect Jira body: %w", err)
}
}
return err
} Prevention
- Validate the API token and username with a /myself call at startup.
- Verify project keys against the Jira admin before first use.
- Never hardcode tokens; rotate before expiry and reload from the secret store.
- Test JQL/queries in the Jira UI before encoding them in code.
- Confirm jira.url matches the correct Cloud site or Server base URL.
When it happens
Trigger: Any doRequest call (issue fetch/create/update, JQL search) where resp.StatusCode is one of 400, 401, 403, 404. The full response body is included in the message.
Common situations: Expired or wrong API token (401); token valid but account lacks permission on the project (403); typo in issue key or project key (404); malformed JQL or JSON payload (400); deleted issue (404); using a Cloud token against Server or vice versa.
Related errors
- Jira API token not configured Run: bd config set jira.api_to
- failed to list projects: %w
- failed to get work item types: %w
- non-retryable error: %w
- failed to fetch issue %d: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/06d625ef67c531be.
Report an issue: GitHub.