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

  1. Open the path named in the message and validate the JSON (python -m json.tool <path>); fix the syntax error or restore a backup.
  2. 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).
  3. 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.
  4. 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

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


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/3e9b823ad9873e70. Report an issue: GitHub.