{"record":{"id":"4057900139853f32","repo":"headroomlabs-ai/headroom","slug":"max-sessions-must-be-0-405790","errorCode":null,"errorMessage":"max_sessions must be > 0","messagePattern":"max_sessions must be > 0","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"headroom/proxy/helpers.py","lineNumber":1652,"sourceCode":"      * Union with previously-seen tokens for this session (sticky-on).\n      * Update the session's seen set.\n      * Return the union (preserving first-seen order).\n\n    Bounded by `max_sessions` (default 1000) via `OrderedDict` LRU\n    eviction: hits move-to-end; overflow pops oldest. Reentrant lock so\n    future callers from inside another locked method don't self-deadlock\n    (mirrors `CompressionCache` pattern).\n\n    The tracker is provider-aware: the same `session_id` for Anthropic\n    and OpenAI keeps independent token sets (clients/upstreams differ on\n    which tokens are valid).\n    \"\"\"\n\n    def __init__(self, max_sessions: int | None = None) -> None:\n        if max_sessions is None:\n            max_sessions = get_beta_tracker_max_sessions()\n        if max_sessions <= 0:\n            raise ValueError(\"max_sessions must be > 0\")\n        self._max_sessions: int = max_sessions\n        # OrderedDict per `compression_cache.py` LRU pattern. Entries\n        # store the per-session ordered token list (preserving first-seen\n        # order). RLock allows future callers from inside another locked\n        # method to enter without self-deadlock.\n        self._lock = threading.RLock()\n        self._sessions: OrderedDict[tuple[str, str], list[str]] = OrderedDict()\n\n    @property\n    def active_sessions(self) -> int:\n        with self._lock:\n            return len(self._sessions)\n\n    def _key(self, provider: str, session_id: str) -> tuple[str, str]:\n        return (provider, session_id)\n\n    def record_and_get_sticky_betas(\n        self,","sourceCodeStart":1634,"sourceCodeEnd":1670,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/proxy/helpers.py#L1634-L1670","documentation":"This is the proxy-side beta-header session tracker in helpers.py: an LRU keyed by (provider, session_id) storing per-session ordered beta-token lists. Its constructor defaults max_sessions from get_beta_tracker_max_sessions() (i.e. HEADROOM_BETA_TRACKER_MAX_SESSIONS, default 1000) and then applies the same positive-bound guard as SessionCcrTracker. A non-positive explicit value raises ValueError at construction, which typically happens during proxy/component wiring.","triggerScenarios":"Constructing the tracker with max_sessions=0 or a negative number directly, or after get_beta_tracker_max_sessions() resolved a bad env value in a context that bypassed resolver validation (e.g. int(os.environ[...]) done manually).","commonSituations":"Setting HEADROOM_BETA_TRACKER_MAX_SESSIONS=0 hoping to disable tracking and constructing the tracker without the validating resolver; test fixtures instantiating with 0; computing the bound from memory pressure heuristics that can hit 0 on small hosts.","solutions":["Pass a positive bound or None (None picks up the validated env/default): BetaHeaderSessionTracker(max_sessions=None).","If deriving the number, clamp before construction: max(1, computed).","Fix the source value: set HEADROOM_BETA_TRACKER_MAX_SESSIONS to a positive int or unset it for the 1000 default; disable the feature via HEADROOM_BETA_HEADER_STICKY=disabled instead of zeroing the bound."],"exampleFix":"# before\ntracker = BetaHeaderSessionTracker(max_sessions=0)\n\n# after\ntracker = BetaHeaderSessionTracker(max_sessions=None)  # env/default 1000","handlingStrategy":"validation","validationCode":"def tracker_bound(max_sessions: int | None) -> int:\n    if max_sessions is None:\n        return get_beta_tracker_max_sessions()  # validated + defaulted\n    if max_sessions <= 0:\n        raise ValueError(\"max_sessions must be > 0\")\n    return max_sessions","typeGuard":"def valid_session_bound(v: object) -> bool:\n    return v is None or (isinstance(v, int) and not isinstance(v, bool) and v > 0)","tryCatchPattern":"try:\n    tracker = BetaHeaderSessionTracker(max_sessions=config.get(\"max_sessions\"))\nexcept ValueError as exc:\n    raise ConfigError(f\"beta tracker config: {exc}\") from exc","preventionTips":["Pass None and let the resolver apply env/default rather than computing bounds yourself.","Disable beta-header features via HEADROOM_BETA_HEADER_STICKY=disabled, never by zeroing the session bound.","Validate all numeric env-derived values in one config-load step at startup."],"tags":["validation","constructor","lru","beta-headers","configuration"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}