{"record":{"id":"4ebfb3751b2f5085","repo":"github/spec-kit","slug":"integration-manifest-recovered-files-at-path-m","errorCode":null,"errorMessage":"Integration manifest 'recovered_files' at {path} must be a list of string paths","messagePattern":"Integration manifest 'recovered_files' at (.+?) must be a list of string paths","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/integrations/manifest.py","lineNumber":505,"sourceCode":"\n        files = data.get(\"files\", {})\n        if not isinstance(files, dict) or not all(\n            isinstance(k, str) and isinstance(v, str) for k, v in files.items()\n        ):\n            raise ValueError(\n                f\"Integration manifest 'files' at {path} must be a \"\n                \"mapping of string paths to string hashes\"\n            )\n\n        inst.version = data.get(\"version\", \"\")\n        inst._installed_at = data.get(\"installed_at\", \"\")\n        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":487,"sourceCodeEnd":523,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/integrations/manifest.py#L487-L523","documentation":"IntegrationManifest.load() validates the optional 'recovered_files' array: it must be a list of strings. It tracks files re-adopted after recovery, so non-string entries (ints, objects, nulls) indicate corruption or schema misuse.","triggerScenarios":"A manifest where recovered_files is a dict, a string, or a list containing non-strings — from manual edits, jq scripts, or a different tool version writing an incompatible shape.","commonSituations":"Hand-merging two manifests; scripts copying recovered_files between files; partial JSON edits.","solutions":["Check that recovered_files is [] or a list of path strings matching tracked files","Safest fix: delete the manifest and re-run specify integration install <key> (load() also self-heals stray entries on next save, but the type error must be fixed first)","Restore a known-good copy from git"],"exampleFix":"# before\n\"recovered_files\": {\"a.md\": true}\n# after\n\"recovered_files\": [\"a.md\"]","handlingStrategy":"validation","validationCode":"recovered = data.get(\"recovered_files\", [])\nok = isinstance(recovered, list) and all(isinstance(p, str) for p in recovered)\nif not ok:\n    data[\"recovered_files\"] = []  # field is optional; reset it\n    rewrite_manifest(data)","typeGuard":"def is_valid_recovered_list(v: object) -> bool:\n    return isinstance(v, list) and all(isinstance(p, str) for p in v)","tryCatchPattern":"try:\n    IntegrationManifest.load(key, root)\nexcept ValueError as exc:\n    if \"recovered_files\" in str(exc):\n        reset_recovered_field_and_reload()\n    else:\n        raise","preventionTips":["Treat recovered_files as CLI-internal; do not edit it","Reset it to [] rather than inventing shapes","Re-run install after recovery to let save() normalize state"],"tags":["manifest","json","schema","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}