{"record":{"id":"bc5cfc6bef88bb8e","repo":"headroomlabs-ai/headroom","slug":"provider-must-be-non-empty","errorCode":null,"errorMessage":"provider must be non-empty","messagePattern":"provider must be non-empty","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"headroom/proxy/ccr_session_tracker.py","lineNumber":31,"sourceCode":"        if max_sessions <= 0:\n            raise ValueError(\"max_sessions must be > 0\")\n        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)","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/proxy/ccr_session_tracker.py#L13-L49","documentation":"SessionCcrTracker.has_done_ccr(provider, session_id) requires both key components to be non-empty strings; an empty provider would create a ('', session_id) LRU entry that collides across all providers and defeats the per-provider isolation the tracker is built for. The guard fires before any dict access, so no state is mutated on failure.","triggerScenarios":"Calling has_done_ccr('', 'sess-1') or has_done_ccr('anthropic', '') — typically because an upstream request arrived with no provider resolved (unknown model routing) or no session identifier (client sent no session header/ID).","commonSituations":"A client that omits the session header entirely; a provider name derived from a model string that failed to map; passing None coerced to '' by an earlier str() conversion; calling the tracker directly in tests with placeholder empty strings.","solutions":["Resolve a real provider name before querying — log the model/route that produced an empty provider.","Generate or require a session ID at the boundary: if the client sends none, mint a UUID per connection instead of passing ''.","In tests, use literal non-empty values like ('anthropic', 'test-session')."],"exampleFix":"# before\ntracker.has_done_ccr(provider_name, request.session_id or '')\n\n# after\nsid = request.session_id or uuid4().hex\ntracker.has_done_ccr(provider_name or 'unknown', sid)","handlingStrategy":"validation","validationCode":"def tracker_key_ok(provider: str, session_id: str) -> bool:\n    return bool(provider) and bool(session_id)","typeGuard":"def has_identity(provider: str | None, session_id: str | None) -> TypeGuard[tuple[str, str]]:\n    return bool(provider) and bool(session_id)","tryCatchPattern":"try:\n    done = tracker.has_done_ccr(provider, session_id)\nexcept ValueError as exc:\n    log.error(\"missing identity for CCR lookup: %s\", exc)\n    done = False  # safe default: treat as not-done","preventionTips":["Validate provider/session identity once at request ingress and reject or mint IDs there.","Never coerce None to '' before tracker calls — guard instead.","Log the route/model when provider resolution yields empty so misrouting is visible."],"tags":["validation","session","provider","tracker"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}