dbt-labs/dbt-core · error · anyhow

expected `<lane>.N` after `-`, got {pre:?} in {semver:?}

Error message

expected `<lane>.N` after `-`, got {pre:?} in {semver:?}

What it means

parse_release_version validates semver pre-release suffixes: after the `-`, the identifier must be `<lane>.N` where lane is one of alpha, beta, rc, preview, dev. This error fires when the pre-release string contains no dot, so no lane/number pair can be split. It guards conversion of semver versions to PEP 440 for PyPI publishing.

Source

Thrown at crates/dbt-ci/src/release_version.rs:69

        None => (semver, None),
    };
    let parts: Vec<&str> = base.split('.').collect();
    let well_formed = parts.len() == 3
        && parts
            .iter()
            .all(|p| !p.is_empty() && p.chars().all(|c| c.is_ascii_digit()));
    if !well_formed {
        bail!("expected X.Y.Z, got {base:?} in {semver:?}");
    }
    let Some(pre) = pre else {
        return Ok(ParsedReleaseVersion {
            base,
            prerelease: None,
        });
    };
    let (lane_str, number) = pre
        .split_once('.')
        .ok_or_else(|| anyhow!("expected `<lane>.N` after `-`, got {pre:?} in {semver:?}"))?;
    let lane = PreReleaseLane::parse(lane_str).ok_or_else(|| {
        anyhow!(
            "unsupported pre-release lane {lane_str:?} in {semver:?}; \
             accepted: alpha, beta, rc, preview, dev"
        )
    })?;
    if number.is_empty() || !number.chars().all(|c| c.is_ascii_digit()) {
        bail!("expected integer after `{lane_str}.`, got {number:?} in {semver:?}");
    }
    Ok(ParsedReleaseVersion {
        base,
        prerelease: Some((lane, number)),
    })
}

pub(crate) fn validate_release_version(semver: &str) -> Result<()> {
    parse_release_version(semver).map(|_| ())
}

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Change the version to the `<lane>.N` form, e.g. `1.2.3-beta` becomes `1.2.3-beta.0` or `1.2.3-rc.1`.
  2. Use only supported lanes: alpha, beta, rc, preview, dev.
  3. If the version comes from a script or git tag, update the generator to always append `.N` after the lane.

Example fix

// before
let v = parse_release_version("1.2.3-beta")?;

// after
let v = parse_release_version("1.2.3-beta.0")?;
Defensive patterns

Strategy: validation

Validate before calling

import re
SEMVER_LANE = re.compile(r'^\d+\.\d+\.\d+-(alpha|beta|rc|preview|dev)\.\d+$')
assert SEMVER_LANE.match(version), f"{version} must be <major>.<minor>.<patch>-<lane>.N"

Type guard

def is_lane_version(v: str) -> bool:
    import re
    return bool(re.match(r'^\d+\.\d+\.\d+-(alpha|beta|rc|preview|dev)\.\d+$', v))

Try / catch

match parse_release_version(&version) {
    Err(e) if e.to_string().contains("`<lane>.N`") => bail!("use -alpha.0 style pre-release: {version}"),
    other => other,
}

Prevention

When it happens

Trigger: Calling parse_release_version (directly or via run, validate_release_version, or semver_to_pep440) with a version whose pre-release part after `-` lacks a `.N` suffix, e.g. `1.2.3-beta` or `1.2.3-rc`.

Common situations: Tagging releases as `v1.2.3-rc` from habit with other tooling; reading a version from git tags or env vars produced by scripts that drop the numeric component; mismatch between semver tooling that allows bare pre-release ids and this stricter lane format.

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 dbt-labs/dbt-core@0267ce9170 (2026-09-07). Data as JSON: /api/errors/ea3427736277c6d2. Report an issue: GitHub.