{"record":{"id":"668877fefb4f2734","repo":"github/spec-kit","slug":"preset-manifest-id-is-already-installed-use","errorCode":null,"errorMessage":"Preset '{manifest.id}' is already installed. Use 'specify preset remove {manifest.id}' first.","messagePattern":"Preset '(.+?)' is already installed\\. Use 'specify preset remove (.+?)' first\\.","errorType":"exception","errorClass":"PresetError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/presets/__init__.py","lineNumber":3567,"sourceCode":"        Returns:\n            Installed preset manifest\n\n        Raises:\n            PresetValidationError: If manifest is invalid or priority is invalid\n            PresetCompatibilityError: If pack is incompatible\n        \"\"\"\n        # Validate priority\n        if priority < 1:\n            raise PresetValidationError(\"Priority must be a positive integer (1 or higher)\")\n\n        manifest_path = source_dir / \"preset.yml\"\n        manifest = PresetManifest(manifest_path)\n\n        self.check_compatibility(manifest, speckit_version)\n\n        if self.registry.is_installed(manifest.id):\n            if not force:\n                raise PresetError(\n                    f\"Preset '{manifest.id}' is already installed. \"\n                    f\"Use 'specify preset remove {manifest.id}' first.\"\n                )\n            self.remove(manifest.id)\n\n        dest_dir = self.presets_dir / manifest.id\n        if dest_dir.exists():\n            shutil.rmtree(dest_dir)\n\n        shutil.copytree(source_dir, dest_dir)\n\n        # Pre-register the preset so that composition resolution can see it\n        # in the priority stack when resolving composed command content.\n        self.registry.add(manifest.id, {\n            \"version\": manifest.version,\n            \"source\": \"local\",\n            \"manifest_hash\": manifest.get_hash(),\n            \"enabled\": True,","sourceCodeStart":3549,"sourceCodeEnd":3585,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/presets/__init__.py#L3549-L3585","documentation":"install_from_directory refuses to install a preset whose manifest id is already present in the registry, unless force=True. This prevents silently overwriting an installed preset; the message points at the removal command. With force=True the manager instead removes the existing installation and continues.","triggerScenarios":"Running a second `specify preset add <same-id>` (or calling install_from_directory without force=True) while the preset id exists in .specify/presets/registry.json — including when the previous install partially failed but registered the id.","commonSituations":"Re-running an install script/idempotent CI step, iterating on a local preset during development, or reinstalling after a failed first attempt that had already pre-registered the entry.","solutions":["Remove first: `specify preset remove <id>` (as the message says), then reinstall.","Or pass force=True to the programmatic call for idempotent installs.","If the preset should not be registered, check for leftover registry entries from a failed install and clean them."],"exampleFix":"# before\nmanager.install_from_directory(src, ver)\n\n# after\nmanager.install_from_directory(src, ver, force=True)","handlingStrategy":"fallback","validationCode":"if manager.registry.is_installed(pack_id):\n    manager.remove(pack_id)  # or choose to skip\nmanager.install_from_directory(source_dir, speckit_version, priority=priority)","typeGuard":null,"tryCatchPattern":"try:\n    manager.install_from_directory(src, ver)\nexcept PresetError as e:\n    if \"already installed\" in str(e):\n        manager.install_from_directory(src, ver, force=True)","preventionTips":["Make install scripts idempotent: check is_installed() or pass force=True.","During preset development, wrap remove+install in one script to avoid stale state.","After a failed install, verify registry state before retrying."],"tags":["preset","install","duplicate","state"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}