{"record":{"id":"37d66d1209534cfb","repo":"Kuberwastaken/claurst","slug":"no-tag-name-in-github-api-response","errorCode":null,"errorMessage":"no tag_name in GitHub API response","messagePattern":"no tag_name in GitHub API response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-rust/crates/cli/src/upgrade.rs","lineNumber":196,"sourceCode":"// ---------------------------------------------------------------------------\n// GitHub API: latest version\n// ---------------------------------------------------------------------------\n\nasync fn fetch_latest_version() -> Result<String> {\n    let url = format!(\"https://api.github.com/repos/{}/releases/latest\", REPO);\n    let client = reqwest::Client::builder()\n        .timeout(Duration::from_secs(10))\n        .user_agent(format!(\"claurst-upgrade/{}\", env!(\"CARGO_PKG_VERSION\")))\n        .build()?;\n    let resp = client.get(&url).send().await?;\n    if !resp.status().is_success() {\n        bail!(\"GitHub API returned {} for {}\", resp.status(), url);\n    }\n    let json: serde_json::Value = resp.json().await?;\n    let tag = json\n        .get(\"tag_name\")\n        .and_then(|v| v.as_str())\n        .ok_or_else(|| anyhow!(\"no tag_name in GitHub API response\"))?;\n    Ok(tag.trim_start_matches('v').to_string())\n}\n\n// ---------------------------------------------------------------------------\n// Download\n// ---------------------------------------------------------------------------\n\nasync fn download_to_file(url: &str, dest: &Path) -> Result<()> {\n    let client = reqwest::Client::builder()\n        .timeout(Duration::from_secs(120))\n        .user_agent(format!(\"claurst-upgrade/{}\", env!(\"CARGO_PKG_VERSION\")))\n        .build()?;\n    let resp = client.get(url).send().await?;\n    if !resp.status().is_success() {\n        bail!(\n            \"Download failed: HTTP {} for {}\\n\\\n             Check that this version exists in the releases page.\",\n            resp.status(),","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/Kuberwastaken/claurst/blob/b0637c97ec34144387cbf2f74f65df6d16a6cef1/src-rust/crates/cli/src/upgrade.rs#L178-L214","documentation":"fetch_latest_version parsed the GitHub releases/latest API response successfully, but the JSON had no string 'tag_name' field. The code uses get(\"tag_name\").and_then(as_str).ok_or_else(anyhow!) to bail. GitHub guarantees tag_name on release objects, so this usually means the response was not the expected release payload (e.g. a rate-limit or error JSON body that still parsed).","triggerScenarios":"GET to the GitHub releases endpoint returns 2xx but the JSON lacks tag_name: an error/rate-limit body, a redirect landing page, or a changed/empty response shape in run_upgrade's version check.","commonSituations":"GitHub API rate limiting returning a JSON error object with an unexpected status handling; API schema change; proxy returning alternate JSON; hitting a mirror of api.github.com.","solutions":["Check GitHub API rate-limit headers (X-RateLimit-Remaining) and wait or authenticate with GITHUB_TOKEN.","Print the raw response body to see what JSON was actually returned.","Verify the releases URL points at api.github.com/repos/<owner>/<repo>/releases/latest.","Check the GitHub API changelog for schema changes to the release object."],"exampleFix":"// before\nlet tag = json.get(\"tag_name\").and_then(|v| v.as_str()).ok_or_else(|| anyhow!(\"no tag_name in GitHub API response\"))?;\n// after\nlet tag = json.get(\"tag_name\")\n    .and_then(|v| v.as_str())\n    .or_else(|| json.get(\"message\").and_then(|m| m.as_str()))\n    .ok_or_else(|| anyhow!(\"no tag_name in GitHub API response: {}\", json))?;","handlingStrategy":"type-guard","validationCode":"// validate response shape before treating it as a release\nif json.get(\"tag_name\").and_then(|v| v.as_str()).is_none() {\n    eprintln!(\"unexpected GitHub response: {}\", json);\n}","typeGuard":"fn extract_tag(json: &serde_json::Value) -> Option<&str> {\n    json.get(\"tag_name\").and_then(|v| v.as_str())\n}","tryCatchPattern":"// surface the full body when the shape is wrong\nlet tag = extract_tag(&json)\n    .ok_or_else(|| anyhow!(\"no tag_name in GitHub API response: {}\", json))?;","preventionTips":["Check X-RateLimit-Remaining and use an authenticated GITHUB_TOKEN for API calls","Log the raw response body when parsing fails","Assert the URL is api.github.com/repos/<owner>/<repo>/releases/latest"],"tags":["github","api","json","upgrade"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"b0637c97ec34144387cbf2f74f65df6d16a6cef1","analyzedAt":"2026-09-10T00:24:58.650Z","contentChangedAt":"2026-09-10T00:24:58.650Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}