{"record":{"id":"897015d7e31980b7","repo":"headroomlabs-ai/headroom","slug":"session-id-must-be-non-empty","errorCode":null,"errorMessage":"session_id must be non-empty","messagePattern":"session_id must be non-empty","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"headroom/proxy/ccr_session_tracker.py","lineNumber":33,"sourceCode":"        self._max_sessions = max_sessions\n        self._lock = threading.RLock()\n        self._sessions: OrderedDict[tuple[str, str], tuple[bool, bytes | None]] = 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 has_done_ccr(self, provider: str, session_id: str) -> bool:\n        \"\"\"Return True when this session has previously performed CCR.\"\"\"\n\n        if not provider:\n            raise ValueError(\"provider must be non-empty\")\n        if not session_id:\n            raise ValueError(\"session_id must be non-empty\")\n        key = self._key(provider, session_id)\n        with self._lock:\n            entry = self._sessions.get(key)\n            if entry is None:\n                return False\n            self._sessions.move_to_end(key)\n            return entry[0]\n\n    def get_golden_tool_bytes(self, provider: str, session_id: str) -> bytes | None:\n        \"\"\"Return recorded golden CCR tool-definition bytes, if any.\"\"\"\n\n        if not provider:\n            raise ValueError(\"provider must be non-empty\")\n        if not session_id:\n            raise ValueError(\"session_id must be non-empty\")\n        key = self._key(provider, session_id)\n        with self._lock:\n            entry = self._sessions.get(key)","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/proxy/ccr_session_tracker.py#L15-L51","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Mint a session ID when the inbound one is missing: sid = headers.get('x-headroom-session-id') or uuid4().hex.","Verify the header name your client sends matches what the extraction code reads.","Skip the CCR fast-path entirely when no session identity exists rather than calling with an empty key."],"exampleFix":"# before\ntracker.has_done_ccr('anthropic', headers.get('x-session-id') or '')\n\n# after\nsid = headers.get('x-headroom-session-id') or uuid4().hex\ntracker.has_done_ccr('anthropic', sid)","handlingStrategy":"validation","validationCode":"sid = (headers.get(\"x-headroom-session-id\") or \"\").strip() or uuid4().hex\nassert sid  # guaranteed non-empty","typeGuard":"def valid_session_id(sid: object) -> bool:\n    return isinstance(sid, str) and bool(sid.strip())","tryCatchPattern":"try:\n    done = tracker.has_done_ccr(provider, session_id)\nexcept ValueError:\n    session_id = uuid4().hex\n    done = False","preventionTips":["Mint server-side session IDs for anonymous clients so the key space is never empty.","Keep a single extraction point for session headers and reuse it across handlers."],"tags":["validation","session","tracker","headers"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}