pypa/pip · error · IDNABidiError

Invalid direction for codepoint at position {idx} in a right

Error message

Invalid direction for codepoint at position {idx} in a right-to-left label

What it means

IDNABidiError from check_bidi Bidi Rule 2: in a right-to-left label every codepoint must have a bidi category in the allowed RTL set {R, AL, AN, EN, ES, CS, ET, ON, BN, NSM}. Any codepoint with a category outside that set (e.g. an L-category Latin letter mixed into an Arabic/Hebrew label) is illegal at the given position.

Source

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

    # Bidi rule 1
    direction = unicodedata.bidirectional(label[0])
    if direction in _bidi_rtl_first:
        rtl = True
    elif direction == "L":
        rtl = False
    else:
        raise IDNABidiError(f"First codepoint in label {label!r} must be directionality L, R or AL")

    valid_ending = False
    number_type: Optional[str] = None
    for idx, cp in enumerate(label, 1):
        direction = unicodedata.bidirectional(cp)

        if rtl:
            # Bidi rule 2
            if direction not in _bidi_rtl_allowed:
                raise IDNABidiError(f"Invalid direction for codepoint at position {idx} in a right-to-left label")
            # Bidi rule 3
            if direction in _bidi_rtl_valid_ending:
                valid_ending = True
            elif direction != "NSM":
                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

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Keep each RTL label script-pure; move Latin fragments to a separate label or replace with a transliteration.
  2. Audit the offending position reported in the message and remove or remap that codepoint.
  3. Run a script-consistency check (idnadata.scripts) before IDNA encoding.

Example fix

# before
idna.encode('العxرب')  # Invalid direction at position of 'x'

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

Strategy: validation

Validate before calling

import unicodedata
_RTL_ALLOWED = {'R','AL','AN','EN','ES','CS','ET','ON','BN','NSM'}
def is_pure_rtl_label(label: str) -> bool:
    return all(unicodedata.bidirectional(c) in _RTL_ALLOWED for c in label)

Type guard

import unicodedata
def is_rtl_label_clean(label: str) -> bool:
    return all(unicodedata.bidirectional(c) in {'R','AL','AN','EN','ES','CS','ET','ON','BN','NSM'} for c in label)

Try / catch

from idna import IDNABidiError
try:
    idna.encode(label)
except IDNABidiError as e:
    if 'Invalid direction' in str(e) and 'right-to-left' in str(e):
        # remove L-category codepoints and retry, or reject
        raise ValueError('mixed scripts in RTL label') from e
    raise

Prevention

When it happens

Trigger: Mixing a Latin/different-script L-category character into an otherwise RTL label, e.g. 'العxرب' (Latin 'x' inside Arabic). The position reported is the offending codepoint's index (1-based).

Common situations: Concatenating user-supplied fragments from different scripts into a single DNS label; brand names that splice a Latin trademark into a localized Arabic/Hebrew subdomain; copy-paste errors inserting invisible L-category characters.

Related errors


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