{"record":{"id":"c1b484dcdac02b35","repo":"Hmbown/CodeWhale","slug":"release-redirect-did-not-resolve-to-a-tag-url-fi","errorCode":null,"errorMessage":"release redirect did not resolve to a tag URL: {final_url}","messagePattern":"release redirect did not resolve to a tag URL: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/cli/src/update.rs","lineNumber":1433,"sourceCode":"    url: &str,\n) -> Result<String> {\n    let response = client\n        .get(url)\n        .send()\n        .with_context(|| format!(\"failed to fetch release redirect from {url}\"))?;\n    let status = response.status();\n    let final_url = response.url().clone();\n    if status.is_success() {\n        if let Some(tag_name) = release_tag_from_github_release_url(&final_url) {\n            return Ok(tag_name);\n        }\n        let body = response\n            .text()\n            .with_context(|| format!(\"failed to read release redirect response from {url}\"))?;\n        if let Some(tag_name) = release_tag_from_github_release_html(&body) {\n            return Ok(tag_name);\n        }\n        bail!(\"release redirect did not resolve to a tag URL: {final_url}\");\n    }\n\n    let body = response\n        .text()\n        .with_context(|| format!(\"failed to read release redirect response from {url}\"))?;\n    bail!(\"failed to fetch release redirect from {url}: HTTP {status}\\n{body}\");\n}\n\nfn release_tag_from_github_release_url(url: &reqwest::Url) -> Option<String> {\n    let segments = url.path_segments()?.collect::<Vec<_>>();\n    segments\n        .windows(3)\n        .find(|window| window[0] == \"releases\" && window[1] == \"tag\")\n        .map(|window| window[2].to_string())\n        .filter(|tag| !tag.is_empty())\n}\n\nfn release_tag_from_github_release_html(body: &str) -> Option<String> {","sourceCodeStart":1415,"sourceCodeEnd":1451,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/cli/src/update.rs#L1415-L1451","documentation":"During a self-update, Codewhale resolves the 'latest' release by following GitHub's /releases/latest redirect and expects the final URL to contain /releases/tag/<tag> (or the tag in the HTML body). This error means the redirect succeeded (HTTP 2xx) but the final URL and the page body both lacked a parseable tag, so the version to install could not be determined.","triggerScenarios":"Fetching https://github.com/<owner>/<repo>/releases/latest returned 200 with a final URL whose path has no 'releases/tag/<non-empty-tag>' 3-segment window and whose HTML does not contain a recognizable tag reference — e.g. GitHub A/B UI changes, an interstitial/login/region block page, or a proxy that rewrites redirect targets.","commonSituations":"GitHub serving a changed page layout to the HTML fallback parser; a corporate proxy or captive portal intercepting the redirect and returning its own 200 page; a mirror or GHE host with different URL structure; a repository whose default 'latest' page is unavailable (no published releases).","solutions":["Retry the update shortly — transient GitHub page/interstitial serving usually resolves itself","Bypass 'latest' resolution by updating to an explicit tag/version if the CLI supports pinning","Inspect what the redirect actually returns: `curl -sIL https://github.com/<owner>/<repo>/releases/latest` and check the final Location/path","Remove proxy/TLS-interception or captive-portal interference from the request path, or configure the CLI's proxy settings","If the issue persists on every run, report it — the URL/HTML tag extraction in release_tag_from_github_release_url/_html needs updating for GitHub's current page shape"],"exampleFix":"# before\n$ codewhale update   # resolves 'latest' via redirect\n\n# after\n$ curl -sIL https://github.com/codewhale/codewhale/releases/latest | grep -i location\n# confirm it ends with /releases/tag/vX.Y.Z, then pin that version in the update","handlingStrategy":"retry","validationCode":"// Pre-flight the redirect shape yourself before running the updater:\nlet resp = client.get(latest_url).send().await?;\nlet final_url = resp.url();\nlet is_tag = final_url.path_segments()\n    .is_some_and(|mut s| s.any(|seg| seg == \"tag\"));\nif resp.status().is_success() && !is_tag {\n    // expect lookup failure; surface a clear message now\n}","typeGuard":null,"tryCatchPattern":"// Wrap latest-tag resolution; on 'did not resolve to a tag URL' retry with backoff,\n// then fall back to a pinned version:\nfor attempt in 0..3 {\n    match resolve_latest_tag(&client).await {\n        Ok(tag) => return Ok(tag),\n        Err(e) if e.to_string().contains(\"did not resolve to a tag URL\") => {\n            tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await;\n        }\n        Err(e) => return Err(e),\n    }\n}\nbail!(\"fall back to explicit --version pin\")","preventionTips":["Pin explicit versions in automation instead of relying on 'latest' redirect parsing","Keep GitHub credentials/proxy configuration consistent for release fetches","Alert on redirect final URLs that are not /releases/tag/* to catch GitHub page changes early"],"tags":["update","github","redirect","release","network"],"backgroundTag":"github-release-lookup-failed","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}