Hmbown/CodeWhale · error · anyhow::Error

GitHub release request failed with HTTP {status}: {body}

Error message

GitHub release request failed with HTTP {status}: {body}

What it means

A reqwest request to the GitHub releases API returned a non-success HTTP status; the message includes the status code and the response body, which for GitHub errors is JSON describing the reason. It is raised by the release helper after reading the body, covering release lookups/creation uniformly (the {description} slot names which request failed).

Source

Thrown at crates/release/src/lib.rs:291

        .await
        .with_context(|| format!("failed to fetch {description} from {url}"))?;
    let status = response.status();
    let body = response
        .text()
        .await
        .with_context(|| format!("failed to read {description} response from {url}"));
    release_response_body(status, body, url, description)
}

fn release_response_body(
    status: reqwest::StatusCode,
    body: Result<String>,
    url: &str,
    description: &str,
) -> Result<String> {
    let body = body.with_context(|| format!("failed to read {description} response from {url}"))?;
    if !status.is_success() {
        bail!("GitHub release request failed with HTTP {status}: {body}");
    }
    Ok(body)
}

#[derive(Deserialize)]
struct ReleaseTag {
    tag_name: String,
}

#[derive(Deserialize)]
struct ReleaseListEntry {
    tag_name: String,
}

/// Extracts the `tag_name` field from a GitHub single-release JSON response.
pub fn latest_tag_from_release_json(body: &str) -> Result<String> {
    let release: ReleaseTag = serde_json::from_str(body).with_context(|| {
        format!("failed to parse release JSON from GitHub API. Response: {body}")

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Read the status and body: 404 → the tag/release does not exist yet (create it or fix the tag name); 401 → fix the token; 403 → check scopes and rate limits
  2. For 403/429/5xx, retry with backoff and honor the Retry-After / X-RateLimit-Reset headers
  3. Verify owner/repo spelling and that the token can access the repository (private repos need repo scope)
  4. Confirm the release does not already exist before creating one (422 validation for duplicates)
Defensive patterns

Strategy: retry

Validate before calling

// For lookups, verify the tag exists first to turn 404s into a clear precondition:
let exists = git_tag_exists(&repo, &tag)?; // `git ls-remote --tags origin <tag>`
if !exists { bail!("tag {tag} not pushed; push before querying its release"); }

Try / catch

let mut backoff = Duration::from_secs(2);
loop {
    match fetch_release(&client, &url).await {
        Ok(resp) => break handle_body(resp).await,
        Err(err) if is_retryable(&err) && backoff <= Duration::from_secs(60) => {
            tokio::time::sleep(backoff).await; // 403 rate limit, 429, 5xx
            backoff *= 2;
        }
        Err(err) => break Err(err), // 404/401: fix tag or token, do not retry
    }
}

Prevention

When it happens

Trigger: 404 when the release or tag does not exist (checked before it was created, or tag name typo'd); 401 when the token is missing/invalid; 403 when the token lacks repo scope or the rate limit is exhausted; 422 for validation errors like duplicate release names; 5xx during GitHub incidents.

Common situations: Publishing flows that query by tag before pushing it; expired or under-scoped GITHUB_TOKEN/PAT; CI hitting the secondary rate limit during release bursts; releases queried for repos the token cannot see (private repo, wrong owner/name).

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/dbfa18a6970700ac. Report an issue: GitHub.