headroomlabs-ai/headroom · error · RolloutConfigurationError

unknown rollout channel {value!r}

Error message

unknown rollout channel {value!r}

What it means

Raised by RolloutChannel.parse() in strict mode when the value is neither a valid enum member nor one of the aliases ('production', 'preview', 'nightly', 'development'), after lowercasing. In non-strict mode it only logs a warning and falls back to STABLE, so the exception surfaces only when the caller demands strict validation (e.g. worker snapshot restore).

Source

Thrown at headroom/rollout.py:59

    def parse(cls, value: str | None, *, strict: bool = False) -> RolloutChannel:
        if not value:
            return cls.STABLE
        normalized = value.strip().lower().replace("-", "_")
        aliases = {
            "prod": cls.STABLE,
            "production": cls.STABLE,
            "preview": cls.BETA,
            "nightly": cls.CANARY,
            "development": cls.DEV,
        }
        if normalized in aliases:
            return aliases[normalized]
        try:
            return cls(normalized)
        except ValueError:
            message = f"unknown rollout channel {value!r}"
            if strict:
                raise RolloutConfigurationError(message) from None
            logger.warning("%s; falling back to 'stable'", message)
            return cls.STABLE

    @property
    def order(self) -> int:
        return {
            RolloutChannel.STABLE: 0,
            RolloutChannel.BETA: 1,
            RolloutChannel.CANARY: 2,
            RolloutChannel.DEV: 3,
        }[self]

    def allows(self, required: RolloutChannel) -> bool:
        return self.order >= required.order


class FeatureDecisionReason(str, Enum):
    DEFAULT = "default"

View on GitHub (pinned to 322425c43b)

Solutions

  1. Use a canonical channel name: 'stable', 'beta', 'canary', 'dev', or an alias 'production'/'preview'/'nightly'/'development'.
  2. Normalize your config's channel vocabulary to these names before parse().
  3. If a bad channel should not be fatal, call parse() without strict=True and accept the STABLE fallback (a warning is logged).

Example fix

# before
channel = RolloutChannel.parse(cfg['channel'], strict=True)  # 'prod'

# after
channel = RolloutChannel.parse('production', strict=True)
Defensive patterns

Strategy: validation

Validate before calling

ALIASES = {'stable', 'beta', 'canary', 'dev', 'production', 'preview', 'nightly', 'development'}
normalized = channel.strip().lower()
if normalized not in ALIASES:
    raise ConfigError(f'unknown rollout channel: {channel!r}')
parsed = RolloutChannel.parse(channel, strict=True)

Type guard

def is_rollout_channel(value: str) -> bool:
    v = (value or '').strip().lower()
    aliases = {'stable', 'beta', 'canary', 'dev', 'production', 'preview', 'nightly', 'development'}
    return v in aliases

Try / catch

from headroom.rollout import RolloutChannel, RolloutConfigurationError

try:
    channel = RolloutChannel.parse(value, strict=True)
except RolloutConfigurationError:
    channel = RolloutChannel.STABLE  # explicit fallback with logging

Prevention

When it happens

Trigger: Calling RolloutChannel.parse('prod', strict=True), parse('staging', strict=True), or parsing a snapshot whose 'channel' field is garbage; non-strict calls never raise — they warn and return STABLE.

Common situations: Abbreviations like 'prod' or 'staging' not in the alias table; a renamed channel in a newer version deserializing an older snapshot; free-text channel fields from user config.

Related errors


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