{"record":{"id":"077387a06409bdef","repo":"headroomlabs-ai/headroom","slug":"unknown-rollout-feature-s-in-source-join","errorCode":null,"errorMessage":"unknown rollout feature(s) in {source}: {', '.join(unknown)}; valid: {valid}","messagePattern":"unknown rollout feature\\(s\\) in (.+?): (.+?); valid: (.+?)","errorType":"exception","errorClass":"RolloutConfigurationError","httpStatus":null,"severity":"error","filePath":"headroom/rollout.py","lineNumber":359,"sourceCode":"                elif _falsey(environ[alias]):\n                    legacy_disabled.add(spec.name)\n        return _resolve_snapshot(\n            channel=self.channel,\n            explicit_requested=set(self.config.explicit_requested),\n            explicit_disabled=set(self.config.explicit_disabled),\n            legacy_requested=legacy_requested,\n            legacy_disabled=legacy_disabled,\n            unsafe=self.unsafe_allow_unstable,\n        )\n\n\ndef _validate_names(names: set[str], *, source: str, strict: bool) -> frozenset[str]:\n    unknown = sorted(names - FEATURES.keys())\n    if unknown:\n        valid = \", \".join(sorted(FEATURES))\n        message = f\"unknown rollout feature(s) in {source}: {', '.join(unknown)}; valid: {valid}\"\n        if strict:\n            raise RolloutConfigurationError(message)\n        logger.warning(\"%s; ignoring unknown names (fail-closed)\", message)\n    return frozenset(names & FEATURES.keys())\n\n\ndef resolve_rollout(\n    environ: Mapping[str, str] | None = None,\n    *,\n    requested: Iterable[str] = (),\n    disabled: Iterable[str] = (),\n    strict: bool = False,\n) -> RolloutSnapshot:\n    \"\"\"Resolve all rollout inputs exactly once into an immutable snapshot.\"\"\"\n\n    env = os.environ if environ is None else environ\n    channel = RolloutChannel.parse(env.get(\"HEADROOM_ROLLOUT_CHANNEL\"), strict=strict)\n    requested_names = _split_names(env.get(\"HEADROOM_FEATURES\")) | {\n        normalized for name in requested if (normalized := name.strip().lower().replace(\"-\", \"_\"))\n    }","sourceCodeStart":341,"sourceCodeEnd":377,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/rollout.py#L341-L377","documentation":"Raised (as RolloutConfigurationError) by _validate_names in headroom/rollout.py when feature names supplied through HEADROOM_FEATURES / HEADROOM_DISABLE_FEATURES environment variables or the requested=/disabled= arguments of resolve_rollout() contain names that are not keys of the FEATURES registry. The message lists the unknown names and the valid set. With strict=True (the snapshot path parses rollout config strictly) it raises; with strict=False it only logs a warning and ignores the unknown names (fail-closed: the feature stays off).","triggerScenarios":"Calling resolve_rollout(requested=['feature_that_does_not_exist']) or setting HEADROOM_FEATURES=nonexistent_flag, or a typo like HEADROOM_FEATURES=conetxt_packing where the registry key is 'context_packing'. The snapshot/strict path (line ~304) always uses strict=True, so any persisted rollout config containing a removed or misspelled feature name raises on load.","commonSituations":"Feature was renamed or removed in a newer headroom version while a stale env var or saved rollout config still names it; CI pipelines that pin HEADROOM_FEATURES from an old runbook; typos in dotenv files.","solutions":["Compare the offending names against the valid list in the error message and fix typos/dashes-vs-underscores.","If the feature disappeared, remove it from HEADROOM_FEATURES / HEADROOM_DISABLE_FEATURES or the requested=/disabled= arguments after checking `headroom` docs or FEATURES keys for the current name.","Pin or upgrade to a headroom version whose FEATURES registry contains the names you use.","If you intentionally pass untrusted/user-supplied names, call resolve_rollout(..., strict=False) so unknown names are ignored with a warning instead of raising."],"exampleFix":"# before\nHEADROOM_FEATURES=conetxt_packing  # typo, raises RolloutConfigurationError\n\n# after\nHEADROOM_FEATURES=context_packing","handlingStrategy":"validation","validationCode":"from headroom.rollout import FEATURES, resolve_rollout\n\nnames = ['context_packing', 'my_flag']\nunknown = [n for n in names if n not in FEATURES]\nif unknown:\n    raise SystemConfigError(f'unknown features: {unknown}')\nsnap = resolve_rollout(requested=names)","typeGuard":"from headroom.rollout import FEATURES\n\ndef is_known_feature(name: str) -> bool:\n    return name in FEATURES","tryCatchPattern":"from headroom.rollout import RolloutConfigurationError\ntry:\n    snap = resolve_rollout(requested=names, strict=True)\nexcept RolloutConfigurationError as e:\n    # message already lists valid names; drop unknowns and retry or fail loudly\n    raise","preventionTips":["Print sorted(FEATURES) once in CI to keep env var lists in sync.","Treat unknown feature names in strict mode as a config-drift signal, not something to catch-and-ignore."],"tags":["rollout","configuration","feature-flags"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}