headroomlabs-ai/headroom · error · ManifestError
deployment profile '{profile}' is corrupt ({path}): {e}
Error message
deployment profile '{profile}' is corrupt ({path}): {e} What it means
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.
Source
Thrown at headroom/install/state.py:99
def load_manifest(profile: str = "default") -> DeploymentManifest | None:
"""Load a deployment manifest when present."""
path = manifest_path(profile)
if not path.exists():
return None
# A present-but-corrupt manifest (partial write, hand-edit, schema drift)
# must not crash callers with a raw traceback — every install lifecycle
# command and the auto-run `init hook ensure` route through here. Raise a
# typed error so callers can report cleanly or degrade gracefully.
try:
payload = json.loads(path.read_text(encoding="utf-8"))
payload["mutations"] = [ManagedMutation(**item) for item in payload.get("mutations", [])]
payload["artifacts"] = [ArtifactRecord(**item) for item in payload.get("artifacts", [])]
if "image" in payload:
payload["image"] = _migrate_deprecated_image(payload["image"])
return DeploymentManifest(**payload)
except (json.JSONDecodeError, ValueError, TypeError, OSError) as e:
raise ManifestError(f"deployment profile '{profile}' is corrupt ({path}): {e}") from e
def list_manifests() -> list[DeploymentManifest]:
"""Load all deployment manifests under the deployment root."""
root = deploy_root()
if not root.exists():
return []
manifests: list[DeploymentManifest] = []
for candidate in sorted(root.glob("*/manifest.json")):
try:
payload = json.loads(candidate.read_text(encoding="utf-8"))
payload["mutations"] = [
ManagedMutation(**item) for item in payload.get("mutations", [])
]
payload["artifacts"] = [ArtifactRecord(**item) for item in payload.get("artifacts", [])]
if "image" in payload:View on GitHub (pinned to 322425c43b)
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.
Example fix
# before
manifest = load_manifest(profile) # crashes pipelines with ManifestError
# after
from headroom.install.state import load_manifest, ManifestError
try:
manifest = load_manifest(profile)
except ManifestError as e:
logger.error("profile %s corrupt (%s); rebuilding", profile, e.__cause__)
rebuild_profile(profile) # delete dir + reinstall, or restore backup Defensive patterns
Strategy: try-catch
Validate before calling
import json
from pathlib import Path
def manifest_loadable(path: Path) -> bool:
try:
json.loads(path.read_text(encoding="utf-8"))
return True
except (json.JSONDecodeError, OSError):
return False
# note: this proves JSON validity only; schema validity is checked by load_manifest itself Try / catch
from headroom.install.state import load_manifest, ManifestError
try:
manifest = load_manifest(profile)
except ManifestError as e:
logger.error("corrupt profile (%s): %s", profile, e.__cause__)
rebuild = input("rebuild profile from scratch? [y/N] ").strip().lower() == "y"
if rebuild:
delete_profile_dir(profile); reinstall(profile)
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Windows environment mutation is missing a variable name
- failed to extract {archive.name}: {e}
- Error: 'claude' not found in PATH.
- Error: 'copilot' not found in PATH.
- Error: 'codex' not found in PATH.
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/3e9b823ad9873e70.
Report an issue: GitHub.