pypa/pip · error · IDNABidiError
Invalid direction for codepoint at position {idx} in a left-
Error message
Invalid direction for codepoint at position {idx} in a left-to-right label What it means
IDNABidiError from check_bidi Bidi Rule 5: in a left-to-right label every codepoint's bidi category must be in the allowed LTR set {L, EN, ES, CS, ET, ON, BN, NSM}. A codepoint outside that set (typically an R/AL/AN RTL character) appearing in a label that started with L makes the label's directionality ambiguous and is rejected at the given position.
Source
Thrown at src/pip/_vendor/idna/core.py:161
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
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).
View on GitHub (pinned to d7d0d0a394)
Solutions
- Keep LTR labels free of R/AL/AN codepoints; move RTL text into its own label.
- Pre-filter input with unicodedata.bidirectional and reject any RTL char in an L-started label.
- Use idna.encode with uts46=True and std3_rules=True to surface the problem earlier as an InvalidCodepoint.
Example fix
# before label = 'testا' idna.check_bidi(label, check_ltr=True) # Invalid direction in LTR label # after label = 'test' idna.check_bidi(label, check_ltr=True)
Defensive patterns
Strategy: validation
Validate before calling
import unicodedata
_LTR_ALLOWED = {'L','EN','ES','CS','ET','ON','BN','NSM'}
def is_pure_ltr_label(label: str) -> bool:
return bool(label) and unicodedata.bidirectional(label[0]) == 'L' and all(unicodedata.bidirectional(c) in _LTR_ALLOWED for c in label) Type guard
import unicodedata
def is_ltr_label_clean(label: str) -> bool:
return all(unicodedata.bidirectional(c) in {'L','EN','ES','CS','ET','ON','BN','NSM'} for c in label) Try / catch
from idna import IDNABidiError
try:
idna.check_bidi(label, check_ltr=True)
except IDNABidiError as e:
if 'left-to-right' in str(e):
# remove RTL codepoints or relocate them to a separate label
raise ValueError('RTL codepoint in LTR label') from e
raise Prevention
- Do not mix RTL characters into L-started labels.
- Pre-filter with unicodedata.bidirectional and reject RTL chars in LTR labels.
- Use idna.encode(uts46=True, std3_rules=True) to surface issues earlier as InvalidCodepoint.
When it happens
Trigger: An L-started label into which an RTL character (Hebrew, Arabic, AN digit) is inserted, e.g. 'testا'. Triggered when check_ltr=True or when the LTR label is being explicitly bidi-checked; also when the label is being checked because it was passed to check_bidi with check_ltr.
Common situations: Usernames or slugs that concatenate ASCII prefixes with localized RTL suffixes; automated domain generators that merge keywords from mixed locales; sanitizer that allows non-ASCII but does not enforce script purity.
Related errors
- First codepoint in label {label!r} must be directionality L,
- Invalid direction for codepoint at position {idx} in a right
- Can not mix numeral types in a right-to-left label
- Label ends with illegal codepoint directionality
- Unknown directionality in label {label!r} at position {idx}
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/c4a534a0eaab640c.json.
Report an issue: GitHub.