{"record":{"id":"81d24f538c3c1937","repo":"langchain-ai/deepagents","slug":"uv-tool-receipt-contains-duplicate-canonical-extra","errorCode":null,"errorMessage":"uv tool receipt contains duplicate canonical extra names","messagePattern":"uv tool receipt contains duplicate canonical extra names","errorType":"exception","errorClass":"ToolRequirementIntrospectionError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/update_check.py","lineNumber":3059,"sourceCode":"        )\n        if unsupported_keys:\n            fields = \", \".join(unsupported_keys)\n            msg = (\n                f\"uv tool receipt requirement {name!r} uses source fields \"\n                f\"that cannot be preserved automatically: {fields}\"\n            )\n            raise ToolRequirementIntrospectionError(msg)\n        extras = entry.get(\"extras\", [])\n        if not isinstance(extras, list) or any(\n            not isinstance(extra, str) or not is_valid_extra_name(extra)\n            for extra in extras\n        ):\n            msg = \"uv tool receipt contains invalid extras on the tool requirement\"\n            raise ToolRequirementIntrospectionError(msg)\n        normalized = {canonicalize_name(extra) for extra in extras}\n        if len(normalized) != len(extras):\n            msg = \"uv tool receipt contains duplicate canonical extra names\"\n            raise ToolRequirementIntrospectionError(msg)\n        return normalized\n\n    msg = f\"uv tool receipt does not contain a {distribution_name!r} requirement\"\n    raise ToolRequirementIntrospectionError(msg)\n\n\ndef _uv_tool_with_packages(\n    *,\n    distribution_name: str = \"deepagents-code\",\n    tool_root: Path | None = None,\n    data: dict[str, Any] | None = None,\n) -> tuple[str, ...]:\n    \"\"\"Return package names recorded as uv tool `--with` requirements.\n\n    uv records the tool's requested requirements in `uv-receipt.toml`. Reading\n    that receipt preserves only packages the user asked uv to keep, avoiding the\n    over-broad fallback of promoting every installed transitive dependency to a\n    top-level `--with` requirement.","sourceCodeStart":3041,"sourceCodeEnd":3077,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/update_check.py#L3041-L3077","documentation":"After canonicalizing extra names (PEP 685 normalization), two or more extras collapse to the same canonical name (e.g. \"All\" and \"all\"), which would produce a duplicated `--extra` flag in the reconstructed install command. The library rejects the receipt rather than emitting a broken command.","triggerScenarios":"_uv_tool_selected_extras builds {canonicalize_name(extra) for extra in extras} and raises when the set size differs from the list length — i.e. duplicate extras differing only by case/punctuation; reached via _uv_tool_install_command, dependency_refresh_dry_run_command, removable_extras, or uninstall_extra_command.","commonSituations":"Receipt hand-edited with both \"all\" and \"ALL\"; a script appending extras on each install without deduplication; a receipt produced by tooling that does not normalize names.","solutions":["Remove duplicate extras in the receipt entry, keeping one canonical form (e.g. `extras = [\"all\"]`).","Regenerate the receipt with a single `--extra` flag per extra: `uv tool install deepagents-code --force --extra all`.","Deduplicate with canonicalized names before writing receipts if you generate them programmatically."],"exampleFix":"// before\n extras = [\"all\", \"ALL\"]\n// after\n extras = [\"all\"]","handlingStrategy":"validation","validationCode":"from packaging.utils import canonicalize_name\nimport tomllib\n\ndef extras_deduped(data: dict, dist: str = 'deepagents-code') -> bool:\n    for entry in (data.get('tool') or {}).get('requirements', []):\n        if isinstance(entry, dict) and entry.get('name') == dist:\n            extras = entry.get('extras', [])\n            return len({canonicalize_name(e) for e in extras}) == len(extras)\n    return True","typeGuard":"def extras_unique(value: object) -> bool:\n    if not isinstance(value, list) or not all(isinstance(e, str) for e in value):\n        return False\n    from packaging.utils import canonicalize_name\n    return len({canonicalize_name(e) for e in value}) == len(value)","tryCatchPattern":"try:\n    extras = _uv_tool_selected_extras()\nexcept ToolRequirementIntrospectionError:\n    subprocess.run(['uv', 'tool', 'install', 'deepagents-code', '--force'], check=True)\n    extras = _uv_tool_selected_extras()","preventionTips":["Deduplicate extras by canonicalized name before writing receipts programmatically.","Never append extras to a receipt repeatedly; recompute the full set each time.","Use one `--extra` flag per unique extra when reinstalling.","Normalize extra casing (PEP 685) in any tooling that generates extras lists."],"tags":["uv","extras","duplicates","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}