{"record":{"id":"dbfa18a6970700ac","repo":"Hmbown/CodeWhale","slug":"github-release-request-failed-with-http-status","errorCode":null,"errorMessage":"GitHub release request failed with HTTP {status}: {body}","messagePattern":"GitHub release request failed with HTTP (.+?): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/release/src/lib.rs","lineNumber":291,"sourceCode":"        .await\n        .with_context(|| format!(\"failed to fetch {description} from {url}\"))?;\n    let status = response.status();\n    let body = response\n        .text()\n        .await\n        .with_context(|| format!(\"failed to read {description} response from {url}\"));\n    release_response_body(status, body, url, description)\n}\n\nfn release_response_body(\n    status: reqwest::StatusCode,\n    body: Result<String>,\n    url: &str,\n    description: &str,\n) -> Result<String> {\n    let body = body.with_context(|| format!(\"failed to read {description} response from {url}\"))?;\n    if !status.is_success() {\n        bail!(\"GitHub release request failed with HTTP {status}: {body}\");\n    }\n    Ok(body)\n}\n\n#[derive(Deserialize)]\nstruct ReleaseTag {\n    tag_name: String,\n}\n\n#[derive(Deserialize)]\nstruct ReleaseListEntry {\n    tag_name: String,\n}\n\n/// Extracts the `tag_name` field from a GitHub single-release JSON response.\npub fn latest_tag_from_release_json(body: &str) -> Result<String> {\n    let release: ReleaseTag = serde_json::from_str(body).with_context(|| {\n        format!(\"failed to parse release JSON from GitHub API. Response: {body}\")","sourceCodeStart":273,"sourceCodeEnd":309,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/release/src/lib.rs#L273-L309","documentation":"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).","triggerScenarios":"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.","commonSituations":"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).","solutions":["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","For 403/429/5xx, retry with backoff and honor the Retry-After / X-RateLimit-Reset headers","Verify owner/repo spelling and that the token can access the repository (private repos need repo scope)","Confirm the release does not already exist before creating one (422 validation for duplicates)"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// For lookups, verify the tag exists first to turn 404s into a clear precondition:\nlet exists = git_tag_exists(&repo, &tag)?; // `git ls-remote --tags origin <tag>`\nif !exists { bail!(\"tag {tag} not pushed; push before querying its release\"); }","typeGuard":null,"tryCatchPattern":"let mut backoff = Duration::from_secs(2);\nloop {\n    match fetch_release(&client, &url).await {\n        Ok(resp) => break handle_body(resp).await,\n        Err(err) if is_retryable(&err) && backoff <= Duration::from_secs(60) => {\n            tokio::time::sleep(backoff).await; // 403 rate limit, 429, 5xx\n            backoff *= 2;\n        }\n        Err(err) => break Err(err), // 404/401: fix tag or token, do not retry\n    }\n}","preventionTips":["Push the tag before querying or creating its release","Check token scopes (repo for private repos) and expiry before release jobs","Honor Retry-After / rate-limit headers in CI release bursts"],"tags":["github","http","release","api","rate-limit"],"backgroundTag":"github-api-error","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}