astral-sh/ruff · error

invalid micro version: {micro}

Error message

invalid micro version: {micro}

What it means

The UP036 (outdated sys.version_info block) rule compares a `sys.version_info` tuple against the target Python version. When parsing the comparison, the third (micro) element must be an integer literal; a non-integer literal (or unresolvable value) makes the comparison undecidable, so the rule bails.

Source

Thrown at crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs:238

    let (py_major, py_minor) = py_version.as_tuple();

    match if_major.cmp(&py_major) {
        Ordering::Less => Ok(true),
        Ordering::Greater => Ok(false),
        Ordering::Equal => {
            let Some(if_minor) = check_version_iter.next() else {
                return Ok(true);
            };
            let Some(if_minor) = if_minor.as_u8() else {
                return Err(anyhow::anyhow!("invalid minor version: {if_minor}"));
            };

            let if_micro = match check_version_iter.next() {
                None => None,
                Some(micro) => match micro.as_u8() {
                    Some(micro) => Some(micro),
                    None => anyhow::bail!("invalid micro version: {micro}"),
                },
            };

            Ok(if or_equal {
                // Ex) `sys.version_info <= 3.8`. If Python 3.8 is the minimum supported version,
                // the condition won't always evaluate to `false`, so we want to return `false`.
                if_minor < py_minor
            } else {
                if let Some(if_micro) = if_micro {
                    // Ex) `sys.version_info < 3.8.3`
                    if_minor < py_minor || if_minor == py_minor && if_micro == 0
                } else {
                    // Ex) `sys.version_info < 3.8`. If Python 3.8 is the minimum supported version,
                    // the condition _will_ always evaluate to `false`, so we want to return `true`.
                    if_minor <= py_minor
                }
            })
        }

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. Write the version comparison with plain integer literals: `sys.version_info < (3, 8)` or `(3, 8, 0)`
  2. Remove the computed/dynamic micro component so UP036 can evaluate the condition
  3. Rewrite the check using `sys.version_info >= (3, 8)` form with `if`/`else` blocks the rule can update

Example fix

# before
if sys.version_info < (3, 8, get_micro()):
    legacy()
# after
if sys.version_info < (3, 8):
    legacy()
Defensive patterns

Strategy: validation

Validate before calling

# Ensure version tuples use plain integer literals:
if sys.version_info < (3, 8):        # OK
    ...
if sys.version_info < (3, 8, f()):   # breaks UP036 analysis
    ...

Prevention

When it happens

Trigger: `version_always_less_than` iterated the version tuple and found a micro component whose `as_u8()` returned None — e.g. `sys.version_info < (3, 8, 'x')`, a computed value like `(3, 8, micro)`, or a float micro — instead of an integer literal.

Common situations: Dynamically computed version tuples, non-literal or malformed version comparisons in vendored/legacy compatibility shims, or code migrated from other tooling.

Related errors


AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-09-05). Data as JSON: /api/errors/96daee72bdb08a03. Report an issue: GitHub.