{"record":{"id":"5df6113950b5bf7c","repo":"astrid-runtime/astrid","slug":"github-release-has-missing-or-empty-tag-name","errorCode":null,"errorMessage":"GitHub release has missing or empty tag_name","messagePattern":"GitHub release has missing or empty tag_name","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/capsule/install_update.rs","lineNumber":65,"sourceCode":"    }\n    if response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS\n        || response.status() == reqwest::StatusCode::FORBIDDEN\n    {\n        bail!(\"GitHub API rate limit exceeded - try again later\");\n    }\n    if !response.status().is_success() {\n        bail!(\"GitHub API returned {}\", response.status());\n    }\n\n    let json: serde_json::Value = response\n        .json()\n        .await\n        .context(\"failed to parse GitHub API response\")?;\n    let tag_name = json\n        .get(\"tag_name\")\n        .and_then(serde_json::Value::as_str)\n        .filter(|s| !s.is_empty())\n        .ok_or_else(|| anyhow::anyhow!(\"GitHub release has missing or empty tag_name\"))?;\n\n    let version_str = strip_version_prefix(tag_name);\n    semver::Version::parse(version_str)\n        .with_context(|| format!(\"GitHub tag '{tag_name}' is not valid semver\"))\n}\n\n/// Check whether a newer version is available from a capsule's source.\npub(super) async fn check_remote_version(\n    client: &reqwest::Client,\n    source: &str,\n    current_version: &str,\n) -> UpdateCheck {\n    let Ok(current) = semver::Version::parse(current_version) else {\n        return UpdateCheck::Failed {\n            reason: format!(\"installed version '{current_version}' is not valid semver\"),\n        };\n    };\n","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/capsule/install_update.rs#L47-L83","documentation":"`fetch_github_latest_version` reads the latest release from the GitHub API and extracts `tag_name` to derive the newest version. It throws when the JSON response has no `tag_name`, a non-string `tag_name`, or an empty one — i.e. the response shape is not a normal published release object.","triggerScenarios":"Querying a repo whose latest release is a draft (drafts omit tag_name in the public API); an empty repository with no releases returning an unexpected object; an API/proxy error body parsed as JSON; the repo's 'latest' endpoint returning a redirect/placeholder payload.","commonSituations":"Repositories that only publish draft releases; monorepos tagging without creating GitHub Releases; corporate proxies or rate-limit responses with JSON error bodies lacking tag_name; freshly created repos checked too early.","solutions":["Publish the release as non-draft with a valid tag_name in the GitHub UI (`gh release create v1.2.3`)","Check the API response directly: `curl https://api.github.com/repos/{org}/{repo}/releases/latest` to see the actual payload","If rate-limited or auth-walled, configure a GitHub token; if the repo has no releases, create one"],"exampleFix":"// before (release left as draft, tag_name absent)\ngh release create v1.2.3 --draft\n// after\ngh release create v1.2.3  // published release exposes tag_name","handlingStrategy":"retry","validationCode":"// Validate the release payload before consuming:\n// let tag = json.get(\"tag_name\").and_then(|v| v.as_str()).filter(|s| !s.is_empty());\n// if tag.is_none() { /* treat as no-release / retry or inspect raw response */ }","typeGuard":"fn valid_tag_name(json: &serde_json::Value) -> Option<&str> {\n    json.get(\"tag_name\")?.as_str().filter(|s| !s.is_empty())\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"missing or empty tag_name\") => {\n        eprintln!(\"No published release found (draft? rate-limited? empty repo). Check the GitHub releases page.\");\n    }\n    other => other?,\n}","preventionTips":["Publish releases (not drafts) with valid non-empty tag_name","Add a GitHub token in CI to avoid rate-limit JSON bodies being misread","Verify the repo actually has releases before automated version checks"],"tags":["github","api","versioning","unexpected-response"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}