{"record":{"id":"bf60e9a309f61d2a","repo":"github/spec-kit","slug":"duplicate-singular-name-name-in-provides-s","errorCode":null,"errorMessage":"Duplicate {singular} name '{name}' in 'provides.{section}'","messagePattern":"Duplicate (.+?) name '(.+?)' in 'provides\\.(.+?)'","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/__init__.py","lineNumber":606,"sourceCode":"            if not isinstance(entry, dict):\n                raise ValidationError(\n                    f\"Each entry in 'provides.{section}' must be a mapping\"\n                )\n            if \"name\" not in entry or \"file\" not in entry:\n                raise ValidationError(f\"{singular.capitalize()} missing 'name' or 'file'\")\n\n            name = entry[\"name\"]\n            if not isinstance(name, str):\n                raise ValidationError(\n                    f\"Invalid {singular} name: expected a string, got {type(name).__name__}\"\n                )\n            if not VALID_EXTENSION_ARTIFACT_NAME_PATTERN.match(name):\n                raise ValidationError(\n                    f\"Invalid {singular} name '{name}': \"\n                    \"must be lowercase alphanumeric with hyphens only\"\n                )\n            if name in seen_names:\n                raise ValidationError(\n                    f\"Duplicate {singular} name '{name}' in 'provides.{section}'\"\n                )\n            seen_names.add(name)\n\n            file_value = entry[\"file\"]\n            reason = relative_extension_path_violation(file_value)\n            if reason:\n                label = repr(file_value) if isinstance(file_value, str) else f\"for {singular} '{name}'\"\n                raise ValidationError(f\"Invalid {singular} 'file' {label}: {reason}\")\n\n            if \"description\" in entry and not isinstance(entry[\"description\"], str):\n                raise ValidationError(\n                    f\"Invalid {singular} description for '{name}': expected a string\"\n                )\n\n            if \"strategy\" in entry:\n                raise ValidationError(\n                    f\"Invalid {singular} entry '{name}': 'strategy' is not authorable for \"","sourceCodeStart":588,"sourceCodeEnd":624,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/__init__.py#L588-L624","documentation":"Two entries in the same provides.templates or provides.scripts section declared the same 'name'. Duplicates are rejected because the preset resolver returns the first matching entry by name (PresetResolver._extension_manifest_declared_template), so a later duplicate would be silently unreachable while still being listed by ExtensionManifest.templates/.scripts — an invisible correctness trap, hence a hard error.","triggerScenarios":"\"templates\": [{\"name\": \"plan\", \"file\": \"a.md\"}, {\"name\": \"plan\", \"file\": \"b.md\"}]. The seen_names set in the artifact validator triggers on the second entry during manifest validation.","commonSituations":"Merging two extensions' manifests by concatenation; copy-pasting an entry and forgetting to change the name; renaming one file but not its manifest name.","solutions":["Give each artifact a unique name within its section, e.g. 'plan' and 'plan-extended'.","If the duplicate was accidental, delete one of the entries.","If you are merging manifests, de-duplicate by name and keep the intended 'file'."],"exampleFix":"// before\n\"templates\": [\n  { \"name\": \"plan\", \"file\": \"templates/plan.md\" },\n  { \"name\": \"plan\", \"file\": \"templates/plan-v2.md\" }\n]\n// after\n\"templates\": [\n  { \"name\": \"plan\", \"file\": \"templates/plan.md\" },\n  { \"name\": \"plan-v2\", \"file\": \"templates/plan-v2.md\" }\n]","handlingStrategy":"validation","validationCode":"for section in (\"templates\", \"scripts\"):\n    names = [e.get(\"name\") for e in manifest.get(\"provides\", {}).get(section, []) if isinstance(e, dict)]\n    dupes = {n for n in names if names.count(n) > 1}\n    assert not dupes, f\"duplicate {section} names: {dupes}\"","typeGuard":"def artifact_names_unique(provides: dict, section: str) -> bool:\n    names = [e.get(\"name\") for e in provides.get(section, []) if isinstance(e, dict)]\n    return len(names) == len(set(names))","tryCatchPattern":"try:\n    ExtensionManifest.load(path)\nexcept ValidationError as e:\n    if \"Duplicate\" in str(e):\n        # rename or delete the later duplicate entry and retry\n        ...","preventionTips":["After merging manifests, de-duplicate artifact names per section.","Remember the resolver only ever uses the first name match.","Add a duplicate-name assertion to manifest CI."],"tags":["extensions","manifest","duplicates","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}