{"record":{"id":"2da60f7d3eb5fdd8","repo":"github/spec-kit","slug":"manifest-at-path-belongs-to-integration-stored","errorCode":null,"errorMessage":"Manifest at {path} belongs to integration {stored_key!r}, not {key!r}","messagePattern":"Manifest at (.+?) belongs to integration (.+?), not (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/integrations/manifest.py","lineNumber":517,"sourceCode":"        inst._files = files\n\n        recovered = data.get(\"recovered_files\", [])\n        if not isinstance(recovered, list) or not all(\n            isinstance(p, str) for p in recovered\n        ):\n            raise ValueError(\n                f\"Integration manifest 'recovered_files' at {path} must be a \"\n                \"list of string paths\"\n            )\n        inst._recovered_files = set(recovered)\n        # Drop any recovered_files entries that don't correspond to tracked\n        # files — defensive against externally-edited or partially-corrupted\n        # manifests. Inconsistent state self-corrects on next save().\n        inst._recovered_files &= set(inst._files.keys())\n\n        stored_key = data.get(\"integration\", \"\")\n        if stored_key and stored_key != key:\n            raise ValueError(\n                f\"Manifest at {path} belongs to integration {stored_key!r}, \"\n                f\"not {key!r}\"\n            )\n\n        return inst\n","sourceCodeStart":499,"sourceCodeEnd":523,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/integrations/manifest.py#L499-L523","documentation":"IntegrationManifest.load() compares the 'integration' field in the JSON with the key argument: if the stored key is non-empty and differs, the file belongs to another integration. This prevents, e.g., the claude manifest being loaded and uninstalled under the copilot key and deleting the wrong files.","triggerScenarios":"IntegrationManifest.load('copilot', root) while .specify/integrations/copilot.manifest.json contains \"integration\": \"claude\" — copied manifests between keys, renamed integration keys, or code hardcoding the wrong key string.","commonSituations":"Copying a manifest file as a template for a new integration and forgetting to update the integration field; renaming an integration key between CLI versions; passing the wrong key in custom scripts.","solutions":["Compare: cat the manifest's \"integration\" field vs the key you passed; use the stored one","If starting fresh for this key, delete the mismatched manifest and re-run specify integration install <key>","Never copy manifests between integration keys without editing the integration field"],"exampleFix":"# before (file copilot.manifest.json)\n{\"integration\": \"claude\", ...}\n# after\n{\"integration\": \"copilot\", ...}","handlingStrategy":"validation","validationCode":"import json\ndata = json.loads(path.read_text(encoding=\"utf-8\"))\nstored = data.get(\"integration\", \"\")\nif stored and stored != key:\n    path.unlink()  # wrong-key manifest; regenerate for this key","typeGuard":"def manifest_matches_key(data: dict, key: str) -> bool:\n    stored = data.get(\"integration\", \"\")\n    return not stored or stored == key","tryCatchPattern":"try:\n    IntegrationManifest.load(key, root)\nexcept ValueError as exc:\n    if \"belongs to integration\" in str(exc):\n        path.unlink()\n        IntegrationManifest.load(key, root)  # fresh start\n    else:\n        raise","preventionTips":["Never copy manifests between integration keys","When renaming an integration key, delete old manifests","Load manifests with the same key used at install time"],"tags":["manifest","integration-key","misconfiguration"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}