{"record":{"id":"b99f75308fb0d4ce","repo":"Hmbown/CodeWhale","slug":"publish-package-list-contains-duplicates-duplica","errorCode":null,"errorMessage":"publish package list contains duplicates: {duplicate_crates}","messagePattern":"publish package list contains duplicates: (.+?)","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"scripts/release/validate-crate-publish-order.py","lineNumber":88,"sourceCode":"        for package in packages\n        if isinstance(package, dict) and isinstance(package.get(\"id\"), str)\n    }\n    missing_ids = [member for member in members if member not in packages_by_id]\n    if missing_ids:\n        raise ValidationError(\n            \"Cargo metadata omits workspace package ids: \" + \", \".join(missing_ids)\n        )\n    return [packages_by_id[member] for member in members]\n\n\ndef validate_order(\n    packages: list[dict[str, Any]], ordered_crates: list[str]\n) -> tuple[str, dict[str, bool]]:\n    duplicate_crates = sorted(\n        {name for name in ordered_crates if ordered_crates.count(name) > 1}\n    )\n    if duplicate_crates:\n        raise ValidationError(\n            \"publish package list contains duplicates: \" + \", \".join(duplicate_crates)\n        )\n\n    names = [package.get(\"name\") for package in packages]\n    if any(not isinstance(name, str) or not name for name in names):\n        raise ValidationError(\"workspace package is missing a name\")\n    if len(set(names)) != len(names):\n        raise ValidationError(\"Cargo metadata contains duplicate workspace package names\")\n\n    versions = sorted({package.get(\"version\") for package in packages})\n    if len(versions) != 1 or not isinstance(versions[0], str) or not versions[0]:\n        rendered = \", \".join(str(version) for version in versions)\n        raise ValidationError(f\"workspace packages have mixed versions: {rendered}\")\n\n    workspace_by_name = {package[\"name\"]: package for package in packages}\n    release_names = sorted(\n        name for name in workspace_by_name if name.startswith(\"codewhale-\")\n    )","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/scripts/release/validate-crate-publish-order.py#L70-L106","documentation":"The positional 'crates' arguments -- the maintained publication order, normally sourced from scripts/release/crates.sh -- contain the same crate name more than once. Duplicates would double-publish and corrupt the position arithmetic used for the topological check, so they are rejected before anything else in validate_order(). The sorted duplicate names are listed in the message.","triggerScenarios":"A copy-paste slip adds a crate twice to the ordered list in scripts/release/crates.sh; a merge conflict resolution keeps both copies of a line; invoking the validator manually and passing a name twice.","commonSituations":"Editing crates.sh while adding a new crate to the release inventory.","solutions":["Remove the duplicate entries named in the message from scripts/release/crates.sh (the message lists them sorted)","Sanity-check the list for dups before release: printf '%s\\n' $(cat scripts/release/crates.sh list) | sort | uniq -d","Re-run scripts/release/validate-crate-publish-order.py to confirm"],"exampleFix":"# scripts/release/crates.sh: before\ncrates=(codewhale-protocol codewhale-tui codewhale-protocol codewhale-cli)\n\n# after\ncrates=(codewhale-protocol codewhale-tui codewhale-cli)","handlingStrategy":"validation","validationCode":"ordered_crates = [\"codewhale-protocol\", \"codewhale-tui\", \"codewhale-cli\"]\nif len(set(ordered_crates)) != len(ordered_crates):\n    from collections import Counter\n    dupes = [c for c, n in Counter(ordered_crates).items() if n > 1]\n    raise SystemExit(f\"duplicate crates in publish list: {dupes}\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep crates.sh as the single source of the ordered list and have CI pass it verbatim to the validator","Review merge conflicts in the crate list carefully -- duplicated lines are the usual origin"],"tags":["release","cargo","duplicates","shell","python"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}