pypa/pip · error · IDNAError
Label must not start or end with a hyphen
Error message
Label must not start or end with a hyphen
What it means
IDNAError from check_hyphen_ok: per RFC 5891 a label must neither start nor end with a hyphen ('-'). Leading/trailing hyphens are reserved shapes conflicts (and historically used for wildcard/underscore-style abuse) and break DNS interoperability.
Source
Thrown at src/pip/_vendor/idna/core.py:203
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")
return True
def check_nfc(label: str) -> None:
"""Require that a label is in Unicode Normalization Form C.
:param label: The label to check.
:raises IDNAError: If ``label`` differs from its NFC normalisation.
"""
if len(label) > _max_input_length:
raise IDNAError("Label too long")
if unicodedata.normalize("NFC", label) != label:
raise IDNAError("Label must be in Normalization Form C")
def valid_contextj(label: str, pos: int) -> bool:
"""Validate the CONTEXTJ rules from :rfc:`5892` Appendix A.
View on GitHub (pinned to d7d0d0a394)
Solutions
- Strip leading/trailing hyphens from each label before encoding: label.strip('-').
- Fix the slugify/sanitizer to never emit a leading or trailing hyphen.
- Treat empty labels (after stripping) as invalid and skip them.
Example fix
# before
idna.encode('-العرب-') # must not start or end with a hyphen
# after
idna.encode('العرب'.strip('-')) Defensive patterns
Strategy: validation
Validate before calling
def trim_hyphen_edges(label: str) -> str:
return label.strip('-') Type guard
def has_no_edge_hyphens(label: str) -> bool:
return bool(label) and label[0] != '-' and label[-1] != '-' Try / catch
from idna import IDNAError
try:
idna.encode(label)
except IDNAError as e:
if 'start or end with a hyphen' in str(e):
label = label.strip('-')
else:
raise Prevention
- slugify output must be .strip('-') before becoming a DNS label.
- Treat post-strip empty labels as invalid and skip them.
- Validate edge characters at the input boundary.
When it happens
Trigger: A label like '-example', 'example-', or '-' being passed through alabel/check_label. Most commonly the trailing case, where a slugify routine appended a hyphen that was never trimmed.
Common situations: slugify() output not stripped of leading/trailing '-'; concatenation 'prefix-' + '' leaving a dangling hyphen; user input normalized to lowercase-then-hyphenated without trimming.
Related errors
- Label has disallowed hyphens in 3rd and 4th position
- Label too long
- Label begins with an illegal combining character
- Label must be in Normalization Form C
- Empty Label
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/0c6ded3ae77d78b0.json.
Report an issue: GitHub.