astrid-runtime/astrid · error

signed channel version '{value}' is not canonical semver

Error message

signed channel version '{value}' is not canonical semver

What it means

`canonical_version` parses a version string with `semver::Version::parse` and then requires that re-serializing the parsed version reproduces the input byte-for-byte. This error means the string is valid semver but not in canonical form — e.g. leading zeros in numeric segments (`v01.2.3` style `1.02.3`), uppercase prerelease/build identifiers, or a redundant prefix. Signed channel metadata must use the canonical spelling so signatures bind to a deterministic version string.

Source

Thrown at crates/astrid-cli/src/commands/update_channel.rs:317

fn is_lower_hex_64(value: &str) -> bool {
    value.len() == 64
        && value
            .bytes()
            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
}

fn is_commit(value: &str) -> bool {
    value.len() == 40
        && value
            .bytes()
            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
}

fn canonical_version(value: &str) -> anyhow::Result<semver::Version> {
    let parsed = semver::Version::parse(value)
        .with_context(|| format!("signed channel version '{value}' is not valid semver"))?;
    ensure!(
        parsed.to_string() == value,
        "signed channel version '{value}' is not canonical semver"
    );
    Ok(parsed)
}

fn canonical_time(value: &str, label: &str) -> anyhow::Result<DateTime<Utc>> {
    let parsed = DateTime::parse_from_rfc3339(value)
        .with_context(|| format!("signed channel {label} is not RFC3339"))?
        .with_timezone(&Utc);
    ensure!(
        parsed.to_rfc3339_opts(SecondsFormat::Secs, true) == value,
        "signed channel {label} is not canonical UTC RFC3339 seconds"
    );
    Ok(parsed)
}

fn validate_targets_for(

View on GitHub (pinned to affd8760f4)

Solutions

  1. Rewrite the version in canonical form: no leading zeros in numeric identifiers, lowercase prerelease identifiers, no leading `v`.
  2. Regenerate or re-sign the channel metadata/pointer with the canonical version string so the signature covers exactly what is serialized.
  3. Fix the producing pipeline (CI release script) to normalize versions through `semver::Version::parse(v).to_string()` before embedding them.
  4. If only the local pointer file is wrong, update it to the exact tag string used by the release endpoint.

Example fix

// before (channel metadata)
"version": "1.02.3-RC1"

// after (canonical semver)
"version": "1.2.3-rc1"
Defensive patterns

Strategy: validation

Validate before calling

// Rust: canonicalize before writing into channel metadata
let v = semver::Version::parse(input)?;
assert_eq!(v.to_string(), input, "version must be canonical semver");

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: `validate_pointer` or `verify_release_extension` receiving a version field like `1.02.3`, `1.2.03`, `1.2.3-alpha.01`, or `V1.2.3` (valid semver, non-canonical serialization).

Common situations: Hand-edited channel pointer files; tooling that zero-pads version numbers; a release pipeline emitting uppercase prerelease tags (`1.2.3-RC1` instead of `1.2.3-rc1`); copy-pasting versions with a leading `v` into a field that forbids it.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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