headroomlabs-ai/headroom · error · ValueError

max_sessions must be > 0

Error message

max_sessions must be > 0

What it means

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.

Source

Thrown at headroom/proxy/helpers.py:1652

      * Union with previously-seen tokens for this session (sticky-on).
      * Update the session's seen set.
      * Return the union (preserving first-seen order).

    Bounded by `max_sessions` (default 1000) via `OrderedDict` LRU
    eviction: hits move-to-end; overflow pops oldest. Reentrant lock so
    future callers from inside another locked method don't self-deadlock
    (mirrors `CompressionCache` pattern).

    The tracker is provider-aware: the same `session_id` for Anthropic
    and OpenAI keeps independent token sets (clients/upstreams differ on
    which tokens are valid).
    """

    def __init__(self, max_sessions: int | None = None) -> None:
        if max_sessions is None:
            max_sessions = get_beta_tracker_max_sessions()
        if max_sessions <= 0:
            raise ValueError("max_sessions must be > 0")
        self._max_sessions: int = max_sessions
        # OrderedDict per `compression_cache.py` LRU pattern. Entries
        # store the per-session ordered token list (preserving first-seen
        # order). RLock allows future callers from inside another locked
        # method to enter without self-deadlock.
        self._lock = threading.RLock()
        self._sessions: OrderedDict[tuple[str, str], list[str]] = OrderedDict()

    @property
    def active_sessions(self) -> int:
        with self._lock:
            return len(self._sessions)

    def _key(self, provider: str, session_id: str) -> tuple[str, str]:
        return (provider, session_id)

    def record_and_get_sticky_betas(
        self,

View on GitHub (pinned to 322425c43b)

Solutions

  1. Pass a positive bound or None (None picks up the validated env/default): BetaHeaderSessionTracker(max_sessions=None).
  2. If deriving the number, clamp before construction: max(1, computed).
  3. 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.

Example fix

# before
tracker = BetaHeaderSessionTracker(max_sessions=0)

# after
tracker = BetaHeaderSessionTracker(max_sessions=None)  # env/default 1000
Defensive patterns

Strategy: validation

Validate before calling

def tracker_bound(max_sessions: int | None) -> int:
    if max_sessions is None:
        return get_beta_tracker_max_sessions()  # validated + defaulted
    if max_sessions <= 0:
        raise ValueError("max_sessions must be > 0")
    return max_sessions

Type guard

def valid_session_bound(v: object) -> bool:
    return v is None or (isinstance(v, int) and not isinstance(v, bool) and v > 0)

Try / catch

try:
    tracker = BetaHeaderSessionTracker(max_sessions=config.get("max_sessions"))
except ValueError as exc:
    raise ConfigError(f"beta tracker config: {exc}") from exc

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


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