{"record":{"id":"5ae4da774995cb08","repo":"Hmbown/CodeWhale","slug":"path-lineno-duplicate-alias-alias-r","errorCode":null,"errorMessage":"{path}:{lineno}: duplicate alias {alias!r}","messagePattern":"(.+?):(.+?): duplicate alias (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scripts/check-coauthor-trailers.py","lineNumber":154,"sourceCode":"        raise ValueError(\n            f\"{context}: right-hand email must be numeric GitHub noreply, got {identity.email}\"\n        )\n    return identity\n\n\ndef load_author_map(path: Path) -> dict[str, Identity]:\n    aliases: dict[str, Identity] = {}\n    for lineno, raw_line in enumerate(path.read_text(encoding=\"utf-8\").splitlines(), start=1):\n        line = raw_line.split(\"#\", 1)[0].strip()\n        if not line:\n            continue\n        if \"=\" not in line:\n            raise ValueError(f\"{path}:{lineno}: expected 'alias = Name <email>'\")\n        alias, raw_identity = [part.strip() for part in line.split(\"=\", 1)]\n        identity = parse_identity(raw_identity, f\"{path}:{lineno}\")\n        key = norm_key(alias)\n        if key in aliases and aliases[key] != identity:\n            raise ValueError(f\"{path}:{lineno}: duplicate alias {alias!r}\")\n        aliases[key] = identity\n        aliases.setdefault(norm_key(identity.email), identity)\n        aliases.setdefault(norm_key(identity.name), identity)\n        if login := github_login_from_noreply(identity.email):\n            aliases.setdefault(norm_key(login), identity)\n    return aliases\n\n\ndef git_log(commit_range: str) -> list[Commit]:\n    try:\n        raw = subprocess.check_output(\n            [\n                \"git\",\n                \"log\",\n                \"--format=%H%x00%P%x00%an%x00%ae%x00%s%x00%B%x1e\",\n                commit_range,\n            ],\n            cwd=ROOT,","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/scripts/check-coauthor-trailers.py#L136-L172","documentation":"Aliases in AUTHOR_MAP normalize case-insensitively (norm_key); when the same normalized alias maps to two different identities, the checker raises this duplicate-alias error with the path, line, and alias. It prevents ambiguous resolution when the checker later matches trailers and commit identities back to canonical humans.","triggerScenarios":"Lines like 'alex = Alex A <111+alexa@...>' and later 'Alex = Alex B <222+alexb@...>' — norm_key('alex') collides and the stored Identity differs. Note the reverse-dictionary entries (by email, name, login) use setdefault, so only explicit alias collisions with differing identities raise.","commonSituations":"Two contributors both mapped as 'alex'; adding a new entry whose alias collides with an existing name-key; case differences ('Jane' vs 'jane') that normalize to the same key; duplicate entries for someone who changed their email, mapping both old and new identities under the same alias with a different target.","solutions":["Rename one of the colliding aliases (e.g. 'alexa' and 'alexb') so each normalized key is unique.","If both lines are meant to be the same person, make their identities identical or delete the stale one.","Merge identity updates into a single canonical entry instead of keeping old and new side by side under the same alias.","Re-run the checker; it fails fast at map-load time before any git history is scanned."],"exampleFix":"# before\nalex = Alex A <111+alexa@users.noreply.github.com>\nAlex = Alex B <222+alexb@users.noreply.github.com>\n# after\nalexa = Alex A <111+alexa@users.noreply.github.com>\nalexb = Alex B <222+alexb@users.noreply.github.com>","handlingStrategy":"validation","validationCode":"def norm_key(s: str) -> str:\n    return s.strip().lower()\n\ndef map_has_no_alias_collisions(path) -> bool:\n    seen = {}\n    for line in path.read_text().splitlines():\n        line = line.split(\"#\", 1)[0].strip()\n        if not line or \"=\" not in line:\n            continue\n        alias, ident = (p.strip() for p in line.split(\"=\", 1))\n        key = norm_key(alias)\n        if key in seen and seen[key] != ident:\n            return False\n        seen[key] = ident\n    return True","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Choose case-distinct aliases only after lowercasing — 'Alex' and 'alex' are the same key.","One alias per human; update the existing entry when an identity changes instead of adding a sibling.","Run the checker locally after every AUTHOR_MAP edit."],"tags":["git","author-map","duplicates","validation","ci"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}