pypa/pip · error · IDNAError

Label begins with an illegal combining character

Error message

Label begins with an illegal combining character

What it means

IDNAError from check_initial_combiner: per RFC 5891 §4.2.3.2 a label must not begin with a character of Unicode general category M (Mark – Mn, Mc, Me). A leading combining mark has no base character to combine with and is rejected.

Source

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

    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


def check_hyphen_ok(label: str) -> bool:
    """Validate the hyphen restrictions for a label.

    Per :rfc:`5891` §4.2.3.1 a label must not start or end with a hyphen
    (``U+002D``), and must not have hyphens in both the third and fourth
    positions (the prefix reserved for A-labels).

    :param label: The label to check.
    :returns: ``True`` if the hyphen restrictions are satisfied.
    :raises IDNAError: If any of the hyphen restrictions are violated.
    """
    if label[2:4] == "--":
        raise IDNAError("Label has disallowed hyphens in 3rd and 4th position")
    if label[0] == "-" or label[-1] == "-":
        raise IDNAError("Label must not start or end with a hyphen")

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Ensure the first character of each label is a base character (letter/digit), not a combining mark.
  2. Apply unicodedata.normalize('NFC', label) first so combining marks attach to a base.
  3. Strip leading category-M characters: while unicodedata.category(label[0])[0]=='M': label=label[1:].

Example fix

# before
idna.encode('́test')  # Label begins with illegal combining character

# after
idna.encode('test')
# or pre-normalize:
idna.encode(unicodedata.normalize('NFC', '́test'))  # if a base precedes it
Defensive patterns

Strategy: validation

Validate before calling

import unicodedata
def strip_leading_combiners(label: str) -> str:
    while label and unicodedata.category(label[0])[0] == 'M':
        label = label[1:]
    return label

Type guard

import unicodedata
def starts_with_base_char(label: str) -> bool:
    return bool(label) and unicodedata.category(label[0])[0] != 'M'

Try / catch

from idna import IDNAError
try:
    idna.encode(label)
except IDNAError as e:
    if 'combining character' in str(e):
        label = strip_leading_combiners(label)  # or normalize NFC upstream
    else:
        raise

Prevention

When it happens

Trigger: A label whose first character is a combining mark, e.g. '́abc' (combining acute) or 'ًالعرب' (Arabic tanvin leading). check_initial_combiner is called from check_label during alabel/encode.

Common situations: Strings that lost their leading base character through truncation or normalization; concatenation that puts a combining mark at the start; copy-paste from sources that include leading diacritics; NFC normalization that did not collapse a detached mark.

Related errors


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