pypa/pip · error · IDNABidiError

Label ends with illegal codepoint directionality

Error message

Label ends with illegal codepoint directionality

What it means

IDNABidiError from check_bidi: after scanning all codepoints, the label must end with a codepoint whose bidi category is a valid ending category (R, AL, EN, AN for RTL; L, EN for LTR). Ending with a separator/neutral like ES, CS, ON (e.g. a hyphen, dot-like char, or punctuation) after the last strong character is rejected.

Source

Thrown at src/pip/_vendor/idna/core.py:169

                valid_ending = False
            # Bidi rule 4
            if direction in _bidi_rtl_numeric:
                if not number_type:
                    number_type = direction
                elif number_type != direction:
                    raise IDNABidiError("Can not mix numeral types in a right-to-left label")
        else:
            # Bidi rule 5
            if direction not in _bidi_ltr_allowed:
                raise IDNABidiError(f"Invalid direction for codepoint at position {idx} in a left-to-right label")
            # Bidi rule 6
            if direction in _bidi_ltr_valid_ending:
                valid_ending = True
            elif direction != "NSM":
                valid_ending = False

    if not valid_ending:
        raise IDNABidiError("Label ends with illegal codepoint directionality")

    return True


def check_initial_combiner(label: str) -> bool:
    """Reject labels that begin with a combining mark.

    Per :rfc:`5891` §4.2.3.2 a label must not start with a character of
    Unicode general category ``M`` (Mark).

    :param label: The label to check.
    :returns: ``True`` if the first character is not a combining mark.
    :raises IDNAError: If the label begins with a combining character.
    """
    if unicodedata.category(label[0])[0] == "M":
        raise IDNAError("Label begins with an illegal combining character")
    return True

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Trim trailing separators, hyphens, and neutral/punctuation characters from the label before encoding.
  2. Ensure the last character is a letter (L/R/AL) or number (EN/AN).
  3. Review your slugify/sanitizer to forbid trailing non-alphanumerics.

Example fix

# before
idna.encode('العرب-')  # Label ends with illegal codepoint directionality

# after
idna.encode('العرب')
Defensive patterns

Strategy: validation

Validate before calling

import unicodedata
def has_valid_bidi_ending(label: str) -> bool:
    if not label:
        return False
    d = unicodedata.bidirectional(label[-1])
    return d in {'L','R','AL','EN','AN'}

Type guard

import unicodedata
def ends_with_strong_or_number(label: str) -> bool:
    return bool(label) and unicodedata.bidirectional(label[-1]) in {'L','R','AL','EN','AN'}

Try / catch

from idna import IDNABidiError
try:
    idna.encode(label)
except IDNABidiError as e:
    if 'ends with illegal' in str(e):
        label = label.rstrip('-­.,;:_/')  # trim trailing neutrals
    else:
        raise

Prevention

When it happens

Trigger: A label whose final character is punctuation, a separator, or a symbol rather than a letter or number, e.g. 'الع-' (Arabic label ending in hyphen) or 'abc.' (after the trailing-dot split leaves a punctuation tail).

Common situations: Hyphens appended by a slugify routine that did not trim the tail; trailing combining marks stripped leaving a NSM-neutral final char; concatenation that leaves a separator dangling at the end.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/620eb8f59abf491e.json. Report an issue: GitHub.