BigPizzaV3/CodexPlusPlus · error
release payload missing tag_name
Error message
release payload missing tag_name
What it means
release_from_github_payload parses a GitHub Releases API JSON payload into a Release struct. The library requires the payload to contain a string 'tag_name' field, which is the sole source of the release version. If the field is absent or is not a JSON string (e.g. null, number, object), the parser cannot determine the version and throws this error instead of returning a Release with an empty version.
Solutions
- Inspect the payload with serde_json::to_string_pretty and confirm a string 'tag_name' field exists at the top level before parsing.
- Check the HTTP status before parsing — only feed release_from_github_payload bodies from a 200 response of the releases API endpoint.
- Ensure the release is published (draft releases are not exposed via the public API and other endpoints may return stub bodies).
- For custom/mirrored payloads, normalize them first: inject tag_name from whatever field holds the version.
- Wrap the call and fall back to release_from_latest_json_payload, which accepts 'version' or 'tag_name'.
Example fix
// before
let payload: serde_json::Value = resp.json().await?;
let release = release_from_github_payload(&payload)?;
// after
let payload: serde_json::Value = resp.json().await?;
if payload.get("tag_name").and_then(|v| v.as_str()).is_none() {
anyhow::bail!("unexpected payload, no tag_name: {}", payload);
}
let release = release_from_github_payload(&payload)?; Defensive patterns
Strategy: validation
Validate before calling
fn has_tag_name(payload: &serde_json::Value) -> bool {
payload.get("tag_name").and_then(|v| v.as_str()).map_or(false, |s| !s.is_empty())
}
// call: if !has_tag_name(&payload) { /* fetch correct endpoint or bail */ } Type guard
fn as_tag_name(payload: &serde_json::Value) -> Option<&str> {
payload.get("tag_name")?.as_str()
} Try / catch
match release_from_github_payload(&payload) {
Ok(release) => release,
Err(e) if e.to_string().contains("missing tag_name") => {
// fall back to latest.json parser which also accepts 'version'
release_from_latest_json_payload(&payload)?
}
Err(e) => return Err(e),
} Prevention
- Only feed this parser bodies from a verified-200 GitHub releases API response.
- Validate the payload shape (tag_name present and a string) before parsing.
- Prefer release_from_latest_json_payload for manifests, since it accepts both 'version' and 'tag_name'.
- Keep test fixtures aligned with the real GitHub release schema.
When it happens
Trigger: Calling release_from_github_payload with: (1) a payload from a non-release endpoint (e.g. a rate-limit or error JSON body), (2) a draft release object lacking tag_name, (3) hand-written test/fixture JSON that omits tag_name, or (4) a payload where tag_name is a non-string value.
Common situations: Fetching the GitHub 'latest release' URL while unauthenticated and receiving a 404 JSON body without tag_name; proxy/mirror services returning a wrapped or stripped payload; CI scripts posting partial release metadata; unit-test fixtures missing the field after a schema change.
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 BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/9f3d5ca566d376a0.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/update.rs:77
.split('.')
.map(|part| part.parse::<u64>().map_err(Into::into))
.collect()
}
pub fn is_newer_version(candidate: &str, current: &str) -> anyhow::Result<bool> {
let mut left = parse_version_tag(candidate)?;
let mut right = parse_version_tag(current)?;
let len = left.len().max(right.len());
left.resize(len, 0);
right.resize(len, 0);
Ok(left > right)
}
pub fn release_from_github_payload(payload: &Value) -> anyhow::Result<Release> {
let version = payload
.get("tag_name")
.and_then(Value::as_str)
.ok_or_else(|| anyhow::anyhow!("release payload missing tag_name"))?
.to_string();
let assets = payload
.get("assets")
.and_then(Value::as_array)
.into_iter()
.flatten()
.filter_map(|asset| {
Some((
asset.get("name")?.as_str()?.to_string(),
asset.get("browser_download_url")?.as_str()?.to_string(),
))
})
.collect::<Vec<_>>();
let selected = select_update_asset(&assets);
Ok(Release {
version,
url: payload
.get("html_url")View on GitHub (pinned to b1ed92e5e4)