Hmbown/CodeWhale · error · ValueError
{context}: right-hand email must be numeric GitHub noreply,
Error message
{context}: right-hand email must be numeric GitHub noreply, got {identity.email} What it means
Even when the overall 'Name <email>' shape matches, the email on the right-hand side must be a canonical numeric GitHub noreply address (CANONICAL_NOREPLY_RE: id+login@users.noreply.github.com). This error names the actual email that failed, catching personal addresses, legacy 'login@users.noreply.github.com' forms, and foreign domains.
Source
Thrown at scripts/check-coauthor-trailers.py:136
def norm_key(value: str) -> str:
return value.strip().lower()
def github_login_from_noreply(email: str) -> str | None:
if not CANONICAL_NOREPLY_RE.match(email):
return None
local = email.split("@", 1)[0]
return local.split("+", 1)[1]
def parse_identity(raw: str, context: str) -> Identity:
match = IDENTITY_RE.match(raw)
if not match:
raise ValueError(f"{context}: expected 'Name <id+login@users.noreply.github.com>'")
identity = Identity(match.group("name").strip(), match.group("email").strip())
if not CANONICAL_NOREPLY_RE.match(identity.email):
raise ValueError(
f"{context}: right-hand email must be numeric GitHub noreply, got {identity.email}"
)
return identity
def load_author_map(path: Path) -> dict[str, Identity]:
aliases: dict[str, Identity] = {}
for lineno, raw_line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
line = raw_line.split("#", 1)[0].strip()
if not line:
continue
if "=" not in line:
raise ValueError(f"{path}:{lineno}: expected 'alias = Name <email>'")
alias, raw_identity = [part.strip() for part in line.split("=", 1)]
identity = parse_identity(raw_identity, f"{path}:{lineno}")
key = norm_key(alias)
if key in aliases and aliases[key] != identity:
raise ValueError(f"{path}:{lineno}: duplicate alias {alias!r}")View on GitHub (pinned to 8880682c63)
Solutions
- Replace the email with the contributor's canonical numeric noreply: <userid+login@users.noreply.github.com>.
- Find the numeric id via the GitHub API (api.github.com/users/<login> -> 'id') or from any of their recent commits' noreply addresses.
- Re-run the checker to confirm both AUTHOR_MAP and all trailers in the range validate.
- Configure local git to use the noreply address (git config user.email) for future commits if applicable.
Example fix
# before jane = Jane Doe <jane@example.com> # after jane = Jane Doe <1234567+janedoe@users.noreply.github.com>
Defensive patterns
Strategy: validation
Validate before calling
import re
CANONICAL_NOREPLY_RE = re.compile(
r"^[0-9]+\+[a-zA-Z0-9-]+@users\.noreply\.github\.com$"
)
def email_is_canonical_noreply(email: str) -> bool:
return CANONICAL_NOREPLY_RE.match(email.strip()) is not None Type guard
def is_numeric_noreply(email: object) -> bool:
return isinstance(email, str) and bool(CANONICAL_NOREPLY_RE.match(email.strip())) Prevention
- Use id+login@users.noreply.github.com (numeric id prefix), never personal emails or the legacy login-only form.
- Look up the numeric id once and store it in AUTHOR_MAP so all future trailers validate.
- Reject personal emails at review time rather than letting CI catch them.
When it happens
Trigger: An identity like 'Jane <jane@example.com>' or 'Jane <jane@users.noreply.github.com>' (no numeric id+ prefix) in AUTHOR_MAP or a Co-authored-by trailer. The regex requires the numeric-id-plus-login local part, so any other mailbox form is rejected with the offending value echoed.
Common situations: Contributors supplying their real email in trailers; GitHub's older noreply format without the user id; copy-paste from commits made before GitHub switched to id-prefixed noreply addresses; mailmap-driven rewrites producing legacy forms.
Related errors
- {context}: expected 'Name <id+login@users.noreply.github.com
- {path}:{lineno}: expected 'alias = Name <email>'
- {path}:{lineno}: duplicate alias {alias!r}
- receipt {field} does not match the checked source
- ${label} does not match the authoritative inventory; missing
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/6c177c92698b293b.
Report an issue: GitHub.