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

  1. Replace the email with the contributor's canonical numeric noreply: <userid+login@users.noreply.github.com>.
  2. Find the numeric id via the GitHub API (api.github.com/users/<login> -> 'id') or from any of their recent commits' noreply addresses.
  3. Re-run the checker to confirm both AUTHOR_MAP and all trailers in the range validate.
  4. 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

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


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/6c177c92698b293b. Report an issue: GitHub.