From 7f049203279b3a051ecb94583bfc6edd9158e36e Mon Sep 17 00:00:00 2001 From: Inference Bot Date: Sat, 17 Oct 2026 18:00:49 -0600 Subject: [PATCH] broker: retire the old LiteLLM key before re-creating a same-name key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LiteLLM requires unique key aliases, so re-creating a key with an existing (email, name) pair 400s ('alias already exists') — breaking key rotation from the dashboard. The stored LiteLLM hash lets us retire the old key first; the ON CONFLICT upsert then tracks the new key. Surfaced by the LibreChat sandbox work: the portal chat-key rotation for an SSO member failed with exactly this 400. --- app/main.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/main.py b/app/main.py index cdd131e..46514ea 100644 --- a/app/main.py +++ b/app/main.py @@ -1658,6 +1658,32 @@ async def broker_create_api_key(email: str, name: str) -> dict: team_id = await litellm_get_or_create_team(email, balance) alias = _api_key_alias(email, name) async with httpx.AsyncClient() as client: + # Rotation support: LiteLLM requires unique key aliases, so a re-create + # with an existing (email, name) must retire the old key first. Without + # this, rotating a key from the dashboard 400s ("alias already exists"). + conn = get_db() + row = conn.execute( + "SELECT hash FROM member_api_keys WHERE email = ? AND name = ?", + (email, name), + ).fetchone() + conn.close() + if row and row[0]: + old_hash = row[0] + try: + del_res = await client.post( + f"{LITELLM_BASE}/key/delete", + headers={"Authorization": f"Bearer {LITELLM_MASTER_KEY}"}, + json={"keys": [old_hash]}, + ) + if del_res.status_code == 200: + logger.info("Rotated API key %s for %s (old key retired)", name, email) + else: + logger.warning( + "Old key retire failed for %s/%s: %s %s", + email, name, del_res.status_code, del_res.text[:200], + ) + except Exception as exc: # noqa: BLE001 - rotation is best-effort + logger.warning("Old key retire errored for %s/%s: %s", email, name, exc) r = await client.post( f"{LITELLM_BASE}/key/generate", headers={"Authorization": f"Bearer {LITELLM_MASTER_KEY}"},