headroomlabs-ai/headroom · error · ValueError

session_id must be non-empty

Error message

session_id must be non-empty

What it means

The second guard in SessionCcrTracker.has_done_ccr(): provider was non-empty but session_id is the empty string. An empty session key would make every anonymous request share one LRU entry, corrupting the 'has this session already done CCR' signal. The check is a cheap precondition, not an I/O failure.

Source

Thrown at headroom/proxy/ccr_session_tracker.py:33

        self._max_sessions = max_sessions
        self._lock = threading.RLock()
        self._sessions: OrderedDict[tuple[str, str], tuple[bool, bytes | None]] = 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 has_done_ccr(self, provider: str, session_id: str) -> bool:
        """Return True when this session has previously performed CCR."""

        if not provider:
            raise ValueError("provider must be non-empty")
        if not session_id:
            raise ValueError("session_id must be non-empty")
        key = self._key(provider, session_id)
        with self._lock:
            entry = self._sessions.get(key)
            if entry is None:
                return False
            self._sessions.move_to_end(key)
            return entry[0]

    def get_golden_tool_bytes(self, provider: str, session_id: str) -> bytes | None:
        """Return recorded golden CCR tool-definition bytes, if any."""

        if not provider:
            raise ValueError("provider must be non-empty")
        if not session_id:
            raise ValueError("session_id must be non-empty")
        key = self._key(provider, session_id)
        with self._lock:
            entry = self._sessions.get(key)

View on GitHub (pinned to 322425c43b)

Solutions

  1. Mint a session ID when the inbound one is missing: sid = headers.get('x-headroom-session-id') or uuid4().hex.
  2. Verify the header name your client sends matches what the extraction code reads.
  3. Skip the CCR fast-path entirely when no session identity exists rather than calling with an empty key.

Example fix

# before
tracker.has_done_ccr('anthropic', headers.get('x-session-id') or '')

# after
sid = headers.get('x-headroom-session-id') or uuid4().hex
tracker.has_done_ccr('anthropic', sid)
Defensive patterns

Strategy: validation

Validate before calling

sid = (headers.get("x-headroom-session-id") or "").strip() or uuid4().hex
assert sid  # guaranteed non-empty

Type guard

def valid_session_id(sid: object) -> bool:
    return isinstance(sid, str) and bool(sid.strip())

Try / catch

try:
    done = tracker.has_done_ccr(provider, session_id)
except ValueError:
    session_id = uuid4().hex
    done = False

Prevention

When it happens

Trigger: Calling has_done_ccr('anthropic', '') — e.g. the handler extracted a session ID from a header the client didn't send, or a code path defaults the ID to '' before the tracker call.

Common situations: Clients that don't emit a session/conversation header (curl tests, minimal scripts); a header-name change (x-session-id vs x-headroom-session-id) making the lookup return None which is then coerced to ''; refactors that moved session extraction after the tracker call.

Related errors


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