{"record":{"id":"2c73d1f2bb141879","repo":"headroomlabs-ai/headroom","slug":"unsupported-rollout-worker-schema-version","errorCode":null,"errorMessage":"unsupported rollout worker schema version","messagePattern":"unsupported rollout worker schema version","errorType":"exception","errorClass":"RolloutConfigurationError","httpStatus":null,"severity":"error","filePath":"headroom/rollout.py","lineNumber":292,"sourceCode":"            \"registry_digest\": self.registry_digest,\n            \"snapshot_digest\": self.snapshot_digest,\n            \"channel\": self.channel.value,\n            \"unsafe_allow_unstable\": self.unsafe_allow_unstable,\n            \"explicit_requested\": sorted(self.config.explicit_requested),\n            \"explicit_disabled\": sorted(self.config.explicit_disabled),\n            \"legacy_requested\": sorted(self.config.legacy_requested),\n            \"legacy_disabled\": sorted(self.config.legacy_disabled),\n        }\n\n    @classmethod\n    def from_internal_dict(cls, value: Mapping[str, object]) -> RolloutSnapshot:\n        \"\"\"Validate and restore a snapshot serialized for worker handoff.\"\"\"\n\n        if not isinstance(value, Mapping):\n            raise RolloutConfigurationError(\"invalid rollout worker snapshot\")\n        try:\n            if value.get(\"schema_version\") != ROLLOUT_SCHEMA_VERSION:\n                raise RolloutConfigurationError(\"unsupported rollout worker schema version\")\n            if value.get(\"policy_version\") != ROLLOUT_POLICY_VERSION:\n                raise RolloutConfigurationError(\"rollout worker policy version mismatch\")\n            channel = RolloutChannel.parse(str(value[\"channel\"]), strict=True)\n            unsafe = value[\"unsafe_allow_unstable\"]\n            if not isinstance(unsafe, bool):\n                raise RolloutConfigurationError(\"invalid rollout worker unsafe override\")\n\n            def names(field: str) -> set[str]:\n                raw = value[field]\n                if not isinstance(raw, list) or not all(isinstance(item, str) for item in raw):\n                    raise RolloutConfigurationError(f\"invalid rollout worker field {field!r}\")\n                return set(_validate_names(set(raw), source=field, strict=True))\n\n            snapshot = _resolve_snapshot(\n                channel=channel,\n                explicit_requested=names(\"explicit_requested\"),\n                explicit_disabled=names(\"explicit_disabled\"),\n                legacy_requested=names(\"legacy_requested\"),","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/rollout.py#L274-L310","documentation":"Raised by RolloutSnapshot.from_internal_dict() when the restored dict's 'schema_version' does not equal ROLLOUT_SCHEMA_VERSION. Snapshots are versioned so that handoffs between processes running different headroom builds fail loudly instead of misinterpreting fields.","triggerScenarios":"A proxy running headroom X serializes a snapshot and a worker running headroom Y (different ROLLOUT_SCHEMA_VERSION) calls from_internal_dict() on it; or a hand-crafted dict omits or mutates 'schema_version'.","commonSituations":"Rolling deploys where proxy and worker versions skew; stale snapshots replayed from a queue after an upgrade; tests constructing snapshots by hand with a wrong constant.","solutions":["Align headroom versions across proxy and worker processes (same wheel version).","Drain or discard in-flight snapshots across an upgrade boundary instead of replaying them.","When constructing snapshots in code, emit them via to_internal_dict() rather than hand-writing the dict."],"exampleFix":"# before\nhandoff = {\"schema_version\": 1, ...}  # hardcoded, drifts from library\n\n# after\nhandoff = live_snapshot.to_internal_dict()  # always carries the right schema_version","handlingStrategy":"validation","validationCode":"from headroom.rollout import ROLLOUT_SCHEMA_VERSION\n\nif payload.get('schema_version') != ROLLOUT_SCHEMA_VERSION:\n    raise HandoffVersionError(f'snapshot schema {payload.get(\"schema_version\")} != local {ROLLOUT_SCHEMA_VERSION}')\nsnapshot = RolloutSnapshot.from_internal_dict(payload)","typeGuard":"def snapshot_schema_matches(payload: Mapping) -> bool:\n    from headroom.rollout import ROLLOUT_SCHEMA_VERSION\n    return payload.get('schema_version') == ROLLOUT_SCHEMA_VERSION","tryCatchPattern":"try:\n    snapshot = RolloutSnapshot.from_internal_dict(payload)\nexcept RolloutConfigurationError as e:\n    if 'schema version' in str(e):\n        snapshot = rebuild_snapshot_from_config()  # version skew: rebuild locally\n    else:\n        raise","preventionTips":["Pin identical headroom versions across proxy and worker during rolling deploys.","Always serialize handoff payloads with to_internal_dict(); never hand-build them.","Discard in-flight snapshots at upgrade boundaries rather than replaying them."],"tags":["versioning","serialization","rollout","worker-handoff"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}