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

  1. Open the latest.json being fetched and confirm it has a top-level string 'version' (or 'tag_name') field.
  2. Regenerate/upload the latest.json asset with the required schema: {"version":"x.y.z","assets":[{"name":...,"url":...}]}.
  3. Verify the URL (DEFAULT_LATEST_JSON_URL) actually serves the manifest, not an error body — check HTTP status before parsing.
  4. If the field is a number (e.g. version: 1.2), fix the generator to emit it as a string.
  5. 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

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)