astrid-runtime/astrid · error

GitHub release has missing or empty tag_name

Error message

GitHub release has missing or empty tag_name

What it means

`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.

Solutions

  1. Publish the release as non-draft with a valid tag_name in the GitHub UI (`gh release create v1.2.3`)
  2. Check the API response directly: `curl https://api.github.com/repos/{org}/{repo}/releases/latest` to see the actual payload
  3. If rate-limited or auth-walled, configure a GitHub token; if the repo has no releases, create one

Example fix

// before (release left as draft, tag_name absent)
gh release create v1.2.3 --draft
// after
gh release create v1.2.3  // published release exposes tag_name
Defensive patterns

Strategy: retry

Validate before calling

// Validate the release payload before consuming:
// let tag = json.get("tag_name").and_then(|v| v.as_str()).filter(|s| !s.is_empty());
// if tag.is_none() { /* treat as no-release / retry or inspect raw response */ }

Type guard

fn valid_tag_name(json: &serde_json::Value) -> Option<&str> {
    json.get("tag_name")?.as_str().filter(|s| !s.is_empty())
}

Try / catch

match result {
    Err(e) if e.to_string().contains("missing or empty tag_name") => {
        eprintln!("No published release found (draft? rate-limited? empty repo). Check the GitHub releases page.");
    }
    other => other?,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/5df6113950b5bf7c. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-cli/src/commands/capsule/install_update.rs:65

    }
    if response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS
        || response.status() == reqwest::StatusCode::FORBIDDEN
    {
        bail!("GitHub API rate limit exceeded - try again later");
    }
    if !response.status().is_success() {
        bail!("GitHub API returned {}", response.status());
    }

    let json: serde_json::Value = response
        .json()
        .await
        .context("failed to parse GitHub API response")?;
    let tag_name = json
        .get("tag_name")
        .and_then(serde_json::Value::as_str)
        .filter(|s| !s.is_empty())
        .ok_or_else(|| anyhow::anyhow!("GitHub release has missing or empty tag_name"))?;

    let version_str = strip_version_prefix(tag_name);
    semver::Version::parse(version_str)
        .with_context(|| format!("GitHub tag '{tag_name}' is not valid semver"))
}

/// Check whether a newer version is available from a capsule's source.
pub(super) async fn check_remote_version(
    client: &reqwest::Client,
    source: &str,
    current_version: &str,
) -> UpdateCheck {
    let Ok(current) = semver::Version::parse(current_version) else {
        return UpdateCheck::Failed {
            reason: format!("installed version '{current_version}' is not valid semver"),
        };
    };

View on GitHub (pinned to affd8760f4)