headroomlabs-ai/headroom · error · RolloutConfigurationError

invalid rollout worker unsafe override

Error message

invalid rollout worker unsafe override

What it means

Raised by RolloutSnapshot.from_internal_dict() when the 'unsafe_allow_unstable' field is present but not a bool (e.g. the string 'true' or an int 1). Because this flag bypasses stability protections, the restore path requires an actual JSON boolean rather than a truthy value of another type.

Source

Thrown at headroom/rollout.py:298

            "legacy_requested": sorted(self.config.legacy_requested),
            "legacy_disabled": sorted(self.config.legacy_disabled),
        }

    @classmethod
    def from_internal_dict(cls, value: Mapping[str, object]) -> RolloutSnapshot:
        """Validate and restore a snapshot serialized for worker handoff."""

        if not isinstance(value, Mapping):
            raise RolloutConfigurationError("invalid rollout worker snapshot")
        try:
            if value.get("schema_version") != ROLLOUT_SCHEMA_VERSION:
                raise RolloutConfigurationError("unsupported rollout worker schema version")
            if value.get("policy_version") != ROLLOUT_POLICY_VERSION:
                raise RolloutConfigurationError("rollout worker policy version mismatch")
            channel = RolloutChannel.parse(str(value["channel"]), strict=True)
            unsafe = value["unsafe_allow_unstable"]
            if not isinstance(unsafe, bool):
                raise RolloutConfigurationError("invalid rollout worker unsafe override")

            def names(field: str) -> set[str]:
                raw = value[field]
                if not isinstance(raw, list) or not all(isinstance(item, str) for item in raw):
                    raise RolloutConfigurationError(f"invalid rollout worker field {field!r}")
                return set(_validate_names(set(raw), source=field, strict=True))

            snapshot = _resolve_snapshot(
                channel=channel,
                explicit_requested=names("explicit_requested"),
                explicit_disabled=names("explicit_disabled"),
                legacy_requested=names("legacy_requested"),
                legacy_disabled=names("legacy_disabled"),
                unsafe=unsafe,
            )
        except (KeyError, TypeError) as exc:
            raise RolloutConfigurationError("invalid rollout worker snapshot") from exc
        if value.get("registry_digest") != snapshot.registry_digest:

View on GitHub (pinned to 322425c43b)

Solutions

  1. Produce handoff dicts via to_internal_dict() so the field is a real bool.
  2. Convert strings before restore: unsafe in (True, False) or bool parsing in your adapter.
  3. Never inject env-var strings directly into the handoff payload.

Example fix

# before
payload['unsafe_allow_unstable'] = os.environ.get('UNSAFE', 'false')

# after
payload['unsafe_allow_unstable'] = os.environ.get('UNSAFE', 'false').lower() == 'true'
Defensive patterns

Strategy: type-guard

Validate before calling

unsafe = payload.get('unsafe_allow_unstable')
if not isinstance(unsafe, bool):
    raise ValueError('unsafe_allow_unstable must be a bool')

Type guard

def is_bool_field(value: object) -> bool:
    return isinstance(value, bool)  # note: True/1 are distinct in isinstance(bool, ...)

Try / catch

try:
    snapshot = RolloutSnapshot.from_internal_dict(payload)
except RolloutConfigurationError as e:
    if 'unsafe override' in str(e):
        payload['unsafe_allow_unstable'] = bool(payload['unsafe_allow_unstable'])
        snapshot = RolloutSnapshot.from_internal_dict(payload)
    else:
        raise

Prevention

When it happens

Trigger: from_internal_dict({'unsafe_allow_unstable': 'true', ...}) or 1/0 ints — typical when the dict was built from env vars or form data instead of to_internal_dict().

Common situations: Serializing a snapshot with values sourced from environment variables (always strings); a worker framework coercing booleans to ints; hand-written test fixtures.

Related errors


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