BigPizzaV3/CodexPlusPlus · error
latest.json missing version
Error message
latest.json missing version
What it means
release_from_latest_json_payload parses the latest.json manifest published alongside releases. It looks up the version from 'version' first, falling back to 'tag_name'; both must be strings. If neither field exists (or neither is a string), the release version is unknown and the parser throws this error rather than producing a Release with a bogus version.
Solutions
- Open the latest.json being fetched and confirm it has a top-level string 'version' (or 'tag_name') field.
- Regenerate/upload the latest.json asset with the required schema: {"version":"x.y.z","assets":[{"name":...,"url":...}]}.
- Verify the URL (DEFAULT_LATEST_JSON_URL) actually serves the manifest, not an error body — check HTTP status before parsing.
- If the field is a number (e.g. version: 1.2), fix the generator to emit it as a string.
- For local parsing of third-party manifests, normalize first: copy the version into 'version' before calling the parser.
Example fix
// before
let payload: serde_json::Value = resp.json().await?;
let release = release_from_latest_json_payload(&payload)?;
// after
let payload: serde_json::Value = resp.json().await?;
let payload = match payload.get("version").or_else(|| payload.get("tag_name")) {
Some(v) if v.as_str().is_some() => payload,
_ => return Err(anyhow::anyhow!("latest.json has no version/tag_name: {}", payload)),
};
let release = release_from_latest_json_payload(&payload)?; Defensive patterns
Strategy: validation
Validate before calling
fn latest_json_has_version(payload: &serde_json::Value) -> bool {
["version", "tag_name"].iter().any(|k| {
payload.get(k).and_then(|v| v.as_str()).map_or(false, |s| !s.is_empty())
})
} Type guard
fn manifest_version<'a>(payload: &'a serde_json::Value) -> Option<&'a str> {
payload.get("version").or_else(|| payload.get("tag_name"))?.as_str()
} Try / catch
match release_from_latest_json_payload(&payload) {
Ok(release) => release,
Err(e) if e.to_string().contains("latest.json missing version") => {
eprintln!("manifest schema mismatch: {}", serde_json::to_string(&payload).unwrap_or_default());
return Err(e);
}
Err(e) => return Err(e),
} Prevention
- Pin the latest.json schema ({version, assets:[{name,url}]}) in CI so regeneration can't drop 'version'.
- Check HTTP status is 200 before parsing the fetched manifest.
- Always emit version as a JSON string, never a number.
- Detect empty/error bodies (HTML 404 pages parsed as JSON objects) before calling the parser.
When it happens
Trigger: Calling release_from_latest_json_payload (directly or via fetch_latest_release) with: (1) an empty JSON object, (2) a latest.json generated by a build pipeline that renamed/dropped the 'version' key, (3) an error page or CDN stub returned as JSON, (4) a payload where version/tag_name are numbers rather than strings.
Common situations: A maintainer regenerates latest.json with a new tool that uses a different schema key; the release asset URL points at a 404 HTML/JSON error body; versioning tooling (e.g. cargo-release configs) changed the manifest format; a stale cached manifest from an older schema.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/3d79518e018c2aa6.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/update.rs:114
.and_then(Value::as_str)
.unwrap_or_default()
.to_string(),
body: payload
.get("body")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string(),
asset_name: selected.as_ref().map(|asset| asset.name.clone()),
asset_url: selected.map(|asset| asset.browser_download_url),
})
}
pub fn release_from_latest_json_payload(payload: &Value) -> anyhow::Result<Release> {
let version = payload
.get("version")
.or_else(|| payload.get("tag_name"))
.and_then(Value::as_str)
.ok_or_else(|| anyhow::anyhow!("latest.json missing version"))?
.to_string();
let assets = payload
.get("assets")
.and_then(Value::as_array)
.into_iter()
.flatten()
.filter_map(|asset| {
let name = asset.get("name")?.as_str()?.to_string();
let url = asset
.get("url")
.or_else(|| asset.get("browser_download_url"))?
.as_str()?
.to_string();
Some((name, url))
})
.collect::<Vec<_>>();
let selected = select_update_asset(&assets);
Ok(Release {View on GitHub (pinned to b1ed92e5e4)