{"record":{"id":"3e9b823ad9873e70","repo":"headroomlabs-ai/headroom","slug":"deployment-profile-profile-is-corrupt-path","errorCode":null,"errorMessage":"deployment profile '{profile}' is corrupt ({path}): {e}","messagePattern":"deployment profile '(.+?)' is corrupt \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"ManifestError","httpStatus":null,"severity":"error","filePath":"headroom/install/state.py","lineNumber":99,"sourceCode":"def load_manifest(profile: str = \"default\") -> DeploymentManifest | None:\n    \"\"\"Load a deployment manifest when present.\"\"\"\n\n    path = manifest_path(profile)\n    if not path.exists():\n        return None\n    # A present-but-corrupt manifest (partial write, hand-edit, schema drift)\n    # must not crash callers with a raw traceback — every install lifecycle\n    # command and the auto-run `init hook ensure` route through here. Raise a\n    # typed error so callers can report cleanly or degrade gracefully.\n    try:\n        payload = json.loads(path.read_text(encoding=\"utf-8\"))\n        payload[\"mutations\"] = [ManagedMutation(**item) for item in payload.get(\"mutations\", [])]\n        payload[\"artifacts\"] = [ArtifactRecord(**item) for item in payload.get(\"artifacts\", [])]\n        if \"image\" in payload:\n            payload[\"image\"] = _migrate_deprecated_image(payload[\"image\"])\n        return DeploymentManifest(**payload)\n    except (json.JSONDecodeError, ValueError, TypeError, OSError) as e:\n        raise ManifestError(f\"deployment profile '{profile}' is corrupt ({path}): {e}\") from e\n\n\ndef list_manifests() -> list[DeploymentManifest]:\n    \"\"\"Load all deployment manifests under the deployment root.\"\"\"\n\n    root = deploy_root()\n    if not root.exists():\n        return []\n\n    manifests: list[DeploymentManifest] = []\n    for candidate in sorted(root.glob(\"*/manifest.json\")):\n        try:\n            payload = json.loads(candidate.read_text(encoding=\"utf-8\"))\n            payload[\"mutations\"] = [\n                ManagedMutation(**item) for item in payload.get(\"mutations\", [])\n            ]\n            payload[\"artifacts\"] = [ArtifactRecord(**item) for item in payload.get(\"artifacts\", [])]\n            if \"image\" in payload:","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/install/state.py#L81-L117","documentation":"ManifestError raised by load_manifest when a deployment profile's manifest.json exists but cannot be parsed into a DeploymentManifest — the except explicitly covers json.JSONDecodeError (truncated/partial write), ValueError/TypeError (schema drift: wrong field types, unknown required shapes when constructing ManagedMutation/ArtifactRecord), and OSError (unreadable file). The comment notes every install lifecycle command and the auto-run 'init hook ensure' routes through here, which is why it is a typed error instead of a raw traceback: callers can report or degrade. The message names the profile and the exact path, and __cause__ keeps the underlying reason.","triggerScenarios":"Any headroom install/uninstall/status/init-hook-ensure touching a profile whose manifest.json is malformed: killed mid-write (no atomic rename), hand-edited JSON with a trailing comma, a manifest written by an older headroom with since-changed ManagedMutation/ArtifactRecord fields, or permission errors reading the file.","commonSituations":"Power loss / Ctrl-C during install leaving a partial manifest; headroom upgrades that change the manifest schema; users editing manifests to remove entries; sync tools (Dropbox) holding a lock or syncing a half file.","solutions":["Open the path named in the message and validate the JSON (python -m json.tool <path>); fix the syntax error or restore a backup.","If the JSON is valid but schema-drifted, either upgrade headroom to the matching schema or delete the profile directory and reinstall (manifests are regenerable state).","For repeated partial writes, make installs non-interruptible (no Ctrl-C mid-write) and report the non-atomic write as a bug — manifests should be written atomically.","Catch ManifestError explicitly in automation and treat it as 'profile needs rebuild', not as a crash."],"exampleFix":"# before\nmanifest = load_manifest(profile)  # crashes pipelines with ManifestError\n\n# after\nfrom headroom.install.state import load_manifest, ManifestError\ntry:\n    manifest = load_manifest(profile)\nexcept ManifestError as e:\n    logger.error(\"profile %s corrupt (%s); rebuilding\", profile, e.__cause__)\n    rebuild_profile(profile)  # delete dir + reinstall, or restore backup","handlingStrategy":"try-catch","validationCode":"import json\nfrom pathlib import Path\n\ndef manifest_loadable(path: Path) -> bool:\n    try:\n        json.loads(path.read_text(encoding=\"utf-8\"))\n        return True\n    except (json.JSONDecodeError, OSError):\n        return False\n\n# note: this proves JSON validity only; schema validity is checked by load_manifest itself","typeGuard":null,"tryCatchPattern":"from headroom.install.state import load_manifest, ManifestError\n\ntry:\n    manifest = load_manifest(profile)\nexcept ManifestError as e:\n    logger.error(\"corrupt profile (%s): %s\", profile, e.__cause__)\n    rebuild = input(\"rebuild profile from scratch? [y/N] \").strip().lower() == \"y\"\n    if rebuild:\n        delete_profile_dir(profile); reinstall(profile)\n    else:\n        raise","preventionTips":["Treat manifests as regenerable state: keep install parameters so any profile can be rebuilt.","Catch ManifestError specifically wherever init hooks / lifecycle commands run so corruption degrades instead of crashing.","Don't interrupt installs mid-write; report reproducible corruption on clean installs as a bug."],"tags":["manifest","corruption","installation","persistence","schema-drift"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}