pypa/pip · error · InvalidCodepointContext

Joiner {_unot(cp_value)} not allowed at position {pos + 1} i

Error message

Joiner {_unot(cp_value)} not allowed at position {pos + 1} in {label!r}

What it means

InvalidCodepointContext raised by check_label when valid_contextj returns False for a CONTEXTJ codepoint (U+200C ZERO WIDTH NON-JOINER or U+200D ZERO WIDTH JOINER). Per RFC 5892 Appendix A, these joiners are only legal in specific contexts: ZWJ requires a preceding Virama-combining-class character; ZWNJ requires either a preceding Virama or a joining-type L/D before and R/D after.

Source

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

        raise IDNAError("Empty Label")

    # Reject on domain length rather than label length so support some UTS 46
    # use cases, still reducing processing of label contextual rules
    if not valid_string_length(label, trailing_dot=True):
        raise IDNAError("Label too long")

    check_nfc(label)
    check_hyphen_ok(label)
    check_initial_combiner(label)

    for pos, cp in enumerate(label):
        cp_value = ord(cp)
        if intranges_contain(cp_value, idnadata.codepoint_classes["PVALID"]):
            continue
        if intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTJ"]):
            try:
                if not valid_contextj(label, pos):
                    raise InvalidCodepointContext(f"Joiner {_unot(cp_value)} not allowed at position {pos + 1} in {label!r}")
            except ValueError as err:
                raise IDNAError(
                    f"Unknown codepoint adjacent to joiner {_unot(cp_value)} at position {pos + 1} in {label!r}"
                ) from err
        elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTO"]):
            if not valid_contexto(label, pos):
                raise InvalidCodepointContext(f"Codepoint {_unot(cp_value)} not allowed at position {pos + 1} in {label!r}")
        else:
            raise InvalidCodepoint(f"Codepoint {_unot(cp_value)} at position {pos + 1} of {label!r} not allowed")

    check_bidi(label)


def alabel(label: str) -> bytes:
    """Convert a single U-label into its A-label form.

    The result is the ASCII-Compatible Encoding (ACE) form per :rfc:`5891`
    §4: the label is validated, Punycode-encoded, and prefixed with

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Strip CONTEXTJ codepoints (U+200C, U+200D) from labels that are not legitimately Indic/Arabic.
  2. Restore the required Virama-class or joining-type context around the joiner.
  3. Validate the label's script before allowing joiners (only Devanagari, Arabic, etc., contexts).

Example fix

# before
idna.encode('ab‌cd')  # Joiner U+200C not allowed at position 3

# after
clean = label.replace('‌', '').replace('‍', '')
idna.encode(clean)
Defensive patterns

Strategy: validation

Validate before calling

def strip_stray_joiners(label: str) -> str:
    # only keep U+200C/U+200D when surrounded by legitimate Indic/Arabic context
    return label.replace('\u200c', '').replace('\u200d', '')
def has_safe_contextj(label: str) -> bool:
    from idna.core import valid_contextj
    return all(valid_contextj(label, i) for i, c in enumerate(label) if c in '\u200c\u200d')

Type guard

def has_no_unsupported_joiners(label: str) -> bool:
    # conservative guard: joiners only valid in known Indic/Arabic contexts
    return all(c not in '\u200c\u200d' for c in label) or label.isidentifier() is False

Try / catch

from idna import InvalidCodepointContext, IDNAError
try:
    idna.encode(label)
except InvalidCodepointContext as e:
    if 'Joiner' in str(e):
        label = label.replace('\u200c', '').replace('\u200d', '')  # retry without stray joiners
    else:
        raise

Prevention

When it happens

Trigger: A label containing U+200C or U+200D not surrounded by the required Indic/Arabic joining context, e.g. 'ab‌cd' (ZWNJ with no Virama and no L/D-R/D joiner pairing). The position reported is 1-based.

Common situations: Copy-paste that introduces an invisible ZWJ/ZWNJ (often from emoji sequences or rich text); sanitizers that allow general-category Cf formatting chars; concatenation of Indic fragments that drops the base character the joiner depended on.

Related errors


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