BigPizzaV3/CodexPlusPlus · error · anyhow::Error
Invalid version tag: {value}
Error message
Invalid version tag: {value} What it means
parse_version_tag strips an optional leading v/V, then consumes the longest prefix made only of ASCII digits and dots; if nothing remains it bails with "Invalid version tag: {value}". It is called by is_newer_version(candidate, current) — and therefore by check_for_update — for both the remote release version and the locally compiled-in version. Any string that does not start with a digit after the optional v prefix fails: "dev", "latest", "nightly", "beta2", "".
Source
Thrown at crates/codex-plus-core/src/update.rs:56
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct UpdateInstall {
pub release: Release,
pub installer_path: PathBuf,
pub launched: bool,
}
pub fn parse_version_tag(value: &str) -> anyhow::Result<Vec<u64>> {
let normalized = value.trim().trim_start_matches(['v', 'V']);
let mut digits = String::new();
for ch in normalized.chars() {
if ch.is_ascii_digit() || ch == '.' {
digits.push(ch);
} else {
break;
}
}
if digits.is_empty() {
anyhow::bail!("Invalid version tag: {value}");
}
digits
.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 = payloadView on GitHub (pinned to 1f431ae49b)
Solutions
- Ensure both strings handed to is_newer_version look like vX.Y.Z with a leading digit after the optional v prefix
- Pass the real release tag from latest.json / GitHub instead of a branch or build-channel name
- If arbitrary strings can reach the call, pre-normalize: extract the first digit-and-dot run or default to "0"
- In UI flows, treat a parse failure as 'no update available' instead of propagating the error
Example fix
// before
let newer = is_newer_version(&release.version, current_version)?;
// after
fn safe_is_newer(candidate: &str, current: &str) -> bool {
is_newer_version(candidate, current).unwrap_or(false)
}
let newer = safe_is_newer(&release.version, current_version); Defensive patterns
Strategy: validation
Validate before calling
fn is_parseable_version_tag(value: &str) -> bool {
value
.trim()
.trim_start_matches(['v', 'V'])
.chars()
.next()
.is_some_and(|c| c.is_ascii_digit())
}
assert!(is_parseable_version_tag("v1.2.3"));
assert!(!is_parseable_version_tag("dev")); Prevention
- Stamp builds only with numeric semver versions (CI: git describe --tags --match 'v*')
- Validate manifest versions with the predicate above before driving update logic
- Treat version parse errors as 'no update' rather than hard failures in the UI
When it happens
Trigger: is_newer_version("dev", "0.3.1"), check_for_update("0.0.0-dev"), or parse_version_tag("v") — no digits survive normalization. Tags like "v1.2.3-rc.1" are accepted (parsed as 1.2.3; iteration stops at the first non-digit).
Common situations: Local/debug builds stamped with channel-style versions; a latest.json whose version field holds a name instead of a number; a release tagged "main"; CI passing a branch name instead of the tag into the update check.
Related errors
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16).
Data as JSON: /api/errors/4d0a289b586fdac4.
Report an issue: GitHub.