Skip to content

fix(jwt,otel): backport session conversation id and JWT team header selection to rc/1.103.0 - #43343

Merged
yuneng-berri merged 4 commits into
rc/1.103.0from
litellm_rc_1_103_0_backport_otel_conversation_id_jwt_team
Sep 26, 2026
Merged

yuneng-berri merged 4 commits into
rc/1.103.0from
litellm_rc_1_103_0_backport_otel_conversation_id_jwt_team

Conversation

@yuneng-berri

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • v2 OTel spans on rc/1.103.0 carry no gen_ai.conversation.id
  • JWT callers cannot pick a DB membership team when the token has a team claim

How it solves it:

Intentional product change: with fallback_to_db_teams: true, a header naming a team the caller is not in now gets a 403 reading "among your team memberships" instead of listing the JWT's team ids

User Flow

Before: on rc/1.103.0 a JWT developer and an OTel user each hit a gap that main already fixed

  1. A developer sends POST http://localhost:4000/v1/chat/completions with "litellm_session_id": "conv-1", and their OTel backend shows the span with no gen_ai.conversation.id
  2. A developer whose JWT lists team-eng sends x-litellm-team-id: engineering, that team's alias, and gets 403
  3. A developer whose Entra token resolves appid to a team sends x-litellm-team-id: team-db-1, another team they belong to, with fallback_to_db_teams on, and gets 403

After: the same requests behave the way they do on main

  1. The span for the same request carries gen_ai.conversation.id: "conv-1", and a request with no session id still has none
  2. The alias request returns 200 and is billed to team-eng
  3. The membership request returns 200 and is billed to team-db-1, while a team they are not in still gets 403

Relevant issues

Backport of #42486, #42445, #42495 and #43206

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

tests/test_litellm/proxy/auth and tests/test_litellm/integrations/otel pass on this branch (3067 tests with the local cost map). With the remote cost map, one unrelated imagen pricing test also fails on the rc tip. Each pick's own tests were also run with its source change reverted, and they fail there: #42486 22 failures, #42445 an import error, #42495 4, #43206 3

Adaptations from the main versions:

Type

🆕 New Feature
🐛 Bug Fix

Caveats (if any)

Severe

  • Auth behavior change for JWT deployments with fallback_to_db_teams: true
    • The header can now select any DB membership team, not only the JWT's teams
    • Membership is still checked, and a team the caller is not in still gets 403
  • x-litellm-team-id now also accepts team aliases on every JWT deployment

Medium

devin-ai-integration Bot and others added 4 commits September 26, 2026 12:52
…on v2 LLM spans (#42486)

* feat(otel): emit gen_ai.conversation.id from the caller's session id on v2 LLM spans

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(otel): keep the caller's header session under missing_session_id: generate and read replayed payload session ids

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(otel): drop only the proxy-minted session id so a caller id on the other metadata key survives

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(otel): keep a replayed session id hidden when it only echoes the payload trace id

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(otel): keep a replayed session id even when the payload trace id fell back to it

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(otel): stop reading the replayed payload's session id, the generated marker does not survive replay

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(integration): audit gen_ai.conversation.id on otel v2 spans through a real proxy, sink and postgres

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(integration): keep otel conversation rigs alive for the whole session so shuffled shards do not reboot the proxy per test

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(otel): stop the audit rig proxies from probing sibling test peers for model info

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(otel): record accepted OTLP batches in the sink instead of mutating the collector

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(otel): guard the accepted batch deque so snapshots cannot race sink appends

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: mrinal <mrinal@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: yucheng <yucheng@berri.ai>
(cherry picked from commit a319690)
* fix(jwt): accept a team alias in x-litellm-team-id

The header only matched canonical team ids, so a JWT caller selecting one of their teams by its alias got a 403 even though they belonged to it. The header value is now resolved through the existing alias lookup before the JWT allowed-team check and the DB membership fallback, while a value that is already a team id never costs an alias lookup and denials keep naming the value the caller sent

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(jwt): only alias a header team id the database provably lacks

Under fallback_to_db_teams a header value whose team row read fails for any reason other than TeamNotFoundError now keeps the membership denial instead of falling through to the alias lookup, so a degraded read cannot select a different team that carries the value as an alias. Drops the HeaderTeam docstring that only restated its fields

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: ryan <ryan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit 071cb49)
#42495)

* fix(jwt): say x-litellm-team-id matched no team id or alias in the 403

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(jwt): tell the caller when x-litellm-team-id names an alias shared by several teams

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(jwt): deny a shared x-litellm-team-id alias exactly like an unknown value

A distinct 403 for an alias several teams share was raised before the
allowed-teams check, so any JWT could probe which aliases exist. The
alias lookup now treats the duplicate as a miss, and both denials say
the value does not resolve to a team id or a unique team alias, which
is true for unknown, unauthorized and duplicate values alike

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: ryan <ryan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit 08639fc)
…oken also carries a team claim (#43206)

* fix(jwt): let x-litellm-team-id select DB membership teams when the token also carries a team claim

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* docs(jwt): describe header team selection under fallback_to_db_teams

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit 7b4fd47)
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[High risk] JWT team selection and observability session tracking.

The PR is not ready to merge until the observability tests can be collected and the critical-path database-query requirement is satisfied.

Findings

  1. P1 Observability tests cannot load ▶
  2. P2 Alias headers repeatedly query database ▶

Summary

This backport adds caller session IDs to v2 OTel LLM spans and lets JWT team headers select aliases or, when DB fallback is enabled, DB membership teams.

  • The new observability test module cannot be collected because it imports unavailable test-support symbols.
  • Alias selection under DB fallback adds an avoidable database query to the JWT request path.

Reviews (1) · Last reviewed commit: "fix(jwt): let x-litellm-team-id select D..."

import yaml
from integration._support.client import Gateway, eventually, gateway_from_environment
from integration._support.database import read_rows
from integration._support.process import OwnedProxy, owned_proxy_process

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Observability tests cannot load

When this test file is collected, its import of OwnedProxy and owned_proxy_process fails because integration._support.process defines neither name. None of the new conversation-ID tests can run. The existing owned_proxy helper returns a Gateway, not the process wrapper these tests expect.

Comment on lines +1952 to +1958
await get_team_object(
team_id=header_value,
prisma_client=prisma_client,
user_api_key_cache=user_api_key_cache,
parent_otel_span=parent_otel_span,
proxy_logging_obj=proxy_logging_obj,
team_id_upsert=False,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Alias headers repeatedly query database

With fallback_to_db_teams enabled, a team alias outside the JWT's allowed IDs is first looked up as a team ID. A missing ID raises before that result is cached, so every request with the alias repeats the database query even if the alias itself is cached. This violates the repository directive to avoid new database requests in the critical request path; the requirement must be satisfied before merging.

Rule Used: What: Avoid creating new database requests or Router objects in the critical request path. Why: Creating these objects on every request causes performance degradation and unnecessary resource consumption. (source)

@yuneng-berri
yuneng-berri merged commit 6076c0a into rc/1.103.0 Sep 26, 2026
5 of 6 checks passed
@yuneng-berri
yuneng-berri deleted the litellm_rc_1_103_0_backport_otel_conversation_id_jwt_team branch September 26, 2026 22:03

This branch is waiting to be deployed

1 waiting deployment
e2e-changed — e81362e4 Waiting Sep 26, 2026 by yuneng-berri via Run changed e2e tests against the stage-mirror stack #13016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants