Skip to content

fix(proxy): enforce budgets against authoritative DB spend when the cross-pod counter is stale - #30684

Merged
yassin-berriai merged 1 commit into
litellm_internal_stagingfrom
litellm_enforce_budget_against_stale_counter
Jun 18, 2026
Merged

fix(proxy): enforce budgets against authoritative DB spend when the cross-pod counter is stale#30684
yassin-berriai merged 1 commit into
litellm_internal_stagingfrom
litellm_enforce_budget_against_stale_counter

Conversation

@yassin-berriai

@yassin-berriai yassin-berriai commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Resolves LIT-3772

Linear ticket

https://linear.app/litellm-ai/issue/LIT-3772/virtual-key-spend-limits-are-not-being-enforced

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible; it only solves 1 specific problem

Type

🐛 Bug Fix

Changes

Budget enforcement reads spend through get_current_spend, which returns the cross-pod Redis counter whenever Redis hands back a value. In a multi-worker deployment, a Redis instance that restarts and reloads from an older RDB snapshot can come back serving a stale-low counter; that read is a hit, not a clean miss, so the existing DB reseed never fires and a key keeps getting admitted even though its recorded spend is already over max_budget. The symptom is recorded spend sitting above the limit while requests keep succeeding.

The fix gives get_current_spend an optional max_budget. When the counter would admit the request (counter under budget) and the counter sits below this caller's last-known recorded spend, it re-reads the authoritative DB spend and enforces against the higher value. The guard keeps the DB read off the hot path for healthy keys (a healthy counter tracks at or above recorded spend) and for freshly reset keys (recorded spend is back to 0 after a weekly reset, so there is nothing to floor against), so normal traffic and weekly resets are not over-blocked. The authoritative value is cached in-process for a few seconds, so a persistently stale counter (or a key whose auth-cache spend lags a reset for the 60s auth-cache TTL) drives at most one DB read per counter per window rather than one per request. The hard-budget checks pass their max_budget through across every counter type so none can be bypassed by a stale counter: keys, users, team members, teams, and organizations floor against the DB row; per-window key/team budgets floor against aggregated spend logs; end-user and tag budgets floor against the freshly-loaded recorded spend (they have no DB row). Soft-budget and alert reads keep the cheap counter-only behavior.

How it works

The budget check reads current spend through get_current_spend, which prefers the cross-pod Redis counter. The bug is that a value coming back from Redis is trusted unconditionally, even when Redis restarted and reloaded an older snapshot, so the counter can read lower than the spend already recorded in the database.

Before (over-budget request slips through):

flowchart TD
    R["Request on one of N workers"] --> GC["get_current_spend()"]
    GC --> RC{"Redis counter returns a value?"}
    RC -->|"hit, but stale-low after a Redis restart (e.g. 50)"| ST["current = 50"]
    RC -->|"clean miss"| DBR["reseed from DB (accurate)"]
    ST --> CHK{"current >= max_budget (100)?"}
    DBR --> CHK
    CHK -->|"No (50 < 100)"| ADMIT["admit -> request billed -> recorded spend climbs past budget"]
    CHK -->|"Yes"| REJ["raise BudgetExceededError -> 429"]
Loading

A stale counter never triggers the clean-miss reseed, so enforcement compares a too-low number against the limit and admits the request. Across workers, the ones that read an accurate value reject (429) while the ones that read the stale counter admit and overspend.

After (authoritative re-check before admitting):

flowchart TD
    R["Request on one of N workers"] --> GC["get_current_spend(max_budget)"]
    GC --> CUR["current = counter value (stale 50)"]
    CUR --> G{"max_budget set AND current < max_budget AND recorded_spend > current?"}
    G -->|"No: healthy counter or no budget"| USE["use counter as-is (NO DB read)"]
    G -->|"Yes: counter looks stale-low"| FL["_authoritative_floor_spend()"]
    FL --> CACHE{"DB value cached < 5s?"}
    CACHE -->|"yes"| CV["reuse cached DB value"]
    CACHE -->|"no"| ONE["one DB read, cache for 5s"]
    CV --> MAX["spend = max(counter, DB) = 150"]
    ONE --> MAX
    MAX --> RPR["repair: set-max counter -> 150 (atomic, monotonic) so other workers read the corrected value"]
    USE --> CHK{"spend >= max_budget (100)?"}
    RPR --> CHK
    CHK -->|"Yes (150 >= 100)"| REJ["raise BudgetExceededError -> 429"]
    CHK -->|"No"| ADMIT["admit"]
Loading

The re-check only fires when the counter would admit the request and reads below the spend this worker already knows about (the recorded_spend > current guard), so healthy counters and freshly reset keys stay off the DB path. The authoritative value is cached for 5s, so a persistently stale counter costs at most one DB read per counter per window per worker, not one per request. When the DB value is higher than the counter, the counter is also repaired so a stale-low counter is corrected for every worker (including one whose own cached spend is stale) instead of being re-derived per request. The repair is monotonic and atomic: RedisCache.async_set_max raises the counter only when the new value exceeds the stored one, via a single Lua GET/compare/SET, so a slightly-stale repair can neither lower the counter nor clobber a concurrent increment that racing requests rely on.

Fail-open hardening (reconcile + opt-in fail-closed)

Two further gaps in the fail-open behavior, where the Redis counter is left unset (nil / TTL -2) after a reload:

The post-call reservation reconcile, when it found the counter missing or an adjustment that would drive it negative, deleted the counter and continued ("invalidating reserved counters and continuing"). That deletion is what left counters nil and unenforced after a Redis reload. It now reseeds from the DB's lagging authoritative floor instead of deleting; the monotonic set-max can only raise a stale-low counter, and the read-time floor converges to the true total as the spend buffer flushes. The pre-call admission resize keeps its original fail-closed behavior.

A new opt-in general_settings.fail_closed_budget_enforcement (default false) rejects a request with 503 when the spend backing an admit decision can be verified against neither Redis nor the database, rather than admitting on an unverifiable budget. With it off (default) behavior is unchanged; end-user/tag callers whose spend is loaded fresh from the DB in auth are treated as verified and are not rejected.

Screenshots / Proof of Fix

Every enforcement path was exercised on a live proxy (Postgres + Redis, real gpt-4o-mini traffic, 5433/6380 local containers). For each budgeted entity a budget was driven to its limit, then a Redis restart was simulated by setting the entity's counter to a stale-low value while the recorded spend stayed over budget.

Behavior Result
Key max_budget, counter accurate drove to limit, 429 at the boundary
Key, stale-low Redis counter (restart sim) 429; counter repaired up to the authoritative value
Key, Redis container fully stopped 429 enforced via Postgres
Healthy under-budget key 200 (not over-blocked)
Team max_budget, stale counter (strict) 429 via DB floor; counter repaired
End-user max_budget, stale counter 429 via fresh recorded-spend fallback
Tag max_budget, stale counter 429 via fresh recorded-spend fallback
Per-window budget_limits, stale counter 429 via spend-logs aggregate (after the spend-log flush)
fail_closed_budget_enforcement on, Redis + Postgres both stopped 503 "Budget enforcement unavailable: current spend could not be verified..."

Representative transcripts:

Key over budget while the Redis counter is stale-low (Redis up):

DB spend = 0.0002277,  max_budget = 0.0002
redis-cli SET 'spend:key:<hash>' 0.00001
POST /v1/chat/completions  ->  HTTP 429
{"message":"Budget has been exceeded! Current cost: 0.0002277, Max budget: 0.0002", ...}
# counter repaired: redis-cli GET 'spend:key:<hash>' -> 0.0002277

Budget enforced via Postgres with Redis fully stopped:

docker stop litfix-redis
# over-budget key  -> HTTP 429  ("Current cost: 0.0002277, Max budget: 0.0002")
# fresh under-budget key -> HTTP 200

Team budget, stale counter (strict mode reads the DB floor):

redis-cli SET 'spend:team:<id>' 0.00001
POST /v1/chat/completions  ->  HTTP 429
{"message":"Budget has been exceeded! Team=<id> Current cost: 0.00024, Max budget: 0.0002"}

Fail-closed when nothing can verify spend (Redis + Postgres both down, flag on):

docker stop litfix-redis litfix-pg
POST /v1/chat/completions  ->  HTTP 503
"Budget enforcement unavailable: current spend could not be verified against Redis or the database,
 and fail_closed_budget_enforcement is enabled, so the request was rejected ..."

Unit coverage in tests/test_litellm/proxy/proxy_server/test_spend_counters.py and tests/test_litellm/proxy/test_budget_reservation.py pins each path: the stale-low floor and monotonic repair, the healthy-counter fast path, the no-max_budget bypass, the post-reset admit, the strict-mode floor that closes the both-stale gap, the fail-closed 503, and the reconcile reseed-instead-of-delete. The core tests fail on unfixed code.

@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.

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai

@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.20930% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/caching/redis_cache.py 16.66% 10 Missing ⚠️
litellm/proxy/spend_tracking/budget_reservation.py 90.90% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a budget enforcement gap where a Redis counter that survived a restart and reloaded from an older RDB snapshot returns a stale-low value — a cache hit, not a miss — so the existing DB reseed never fires and over-budget keys keep getting admitted. The fix adds an optional max_budget parameter to get_current_spend that re-checks the authoritative DB floor before admitting an under-budget request, with a 5s in-process cache to bound the extra DB reads, a monotonic async_set_max repair to propagate the correction to other workers, and an opt-in fail_closed_budget_enforcement flag that rejects unverifiable spends with 503 instead of admitting.

  • Stale counter floor: _authoritative_floor_spend reads the DB row (or spend-log aggregate for window counters) when the counter would admit the request and looks stale-low; result is cached 5s per counter per worker and repaired into Redis via atomic Lua GET/compare/SET.
  • Reconcile reseed: post-call reservation reconcile now reseeds from the DB floor instead of deleting the counter and failing open when the counter is missing or an adjustment would underflow, closing the fail-open that left budgets unenforced after a Redis reload.
  • Fail-closed strict mode: general_settings.fail_closed_budget_enforcement (default off) closes the both-stale gap by always running the authoritative re-check, rejecting with 503 when neither Redis nor the DB can verify spend.

Confidence Score: 5/5

Safe to merge. The fix correctly addresses a real enforcement bypass without changing default behavior for healthy counters, per-reset keys, or soft-budget reads, and all new code paths are covered by unit tests that fail on unfixed code.

The implementation is consistent and well-bounded: the authoritative re-check is guarded by current < max_budget (already-rejected traffic never hits the DB), and by the 5s in-process cache (persistently stale counters cost at most one extra DB read per counter per worker per 5s window, not one per request). The monotonic async_set_max Lua script is atomic and correctly short-circuits the EXPIRE when TTL is 0. The SpendCounterReseed.from_db short-circuits immediately for end-user and tag prefixes, so those paths add no DB round-trips. The reconcile behavioral change (reseed-from-DB instead of delete-and-raise) is consistent with the stated goal and the updated tests correctly capture the new contract. No test assertions were weakened.

No files require special attention. litellm/proxy/proxy_server.py has the highest complexity (the new get_current_spend branching tree and its helpers) but the logic is thoroughly covered by the 12 new unit tests in test_spend_counters.py.

Important Files Changed

Filename Overview
litellm/caching/redis_cache.py Adds async_set_max: an atomic Lua-based monotonic write that only raises a counter, never lowers it. TTL defaults to 60s (matching async_increment behavior) and is conditionally applied only when ARGV[2] > 0, preserving correct TTL semantics. Circuit-breaker guard applied.
litellm/proxy/proxy_server.py Core of the fix: get_current_spend gains an optional max_budget parameter that triggers a re-read against the authoritative DB floor before admitting an under-budget request. Adds _authoritative_floor_spend (5s in-process cache, falls back to spend-logs aggregates for window counters), _repair_stale_spend_counter (monotonic Redis write via async_set_max), reseed_spend_counter_from_db (reseeds on reconcile inconsistency), and the opt-in fail_closed_budget_enforcement 503 path.
litellm/proxy/auth/auth_checks.py All get_current_spend call sites updated to pass max_budget; end-user and tag callers also set fallback_authoritative=True. Window budget callers pass window_entity_type, window_entity_id, and window_start so stale-low window counters can be floored against spend-log aggregates.
litellm/proxy/spend_tracking/budget_reservation.py Replaces fail-and-delete on inconsistent reservation counter with reseed-from-DB for post-call reconcile, and keeps fail-closed behavior (raise) for pre-call resize. _ensure_counter_can_apply_adjustment refactored to _counter_can_apply_adjustment returning bool.
litellm/proxy/auth/user_api_key_auth.py One-line change: max_budget=team_member_budget threaded through to get_current_spend for the in-auth team member budget check.
tests/test_litellm/proxy/proxy_server/test_spend_counters.py Adds 12 new unit tests covering all new code paths: stale-low floor, healthy fast path, no-budget bypass, post-reset admit, 5s cache coalescing, end-user/tag fallback, window spend-log floor, fail-closed 503, fail-closed pass-through for authoritative fallback, and strict-mode both-stale gap.
tests/test_litellm/proxy/test_budget_reservation.py Three tests renamed and updated to verify new reconcile behavior (reseed-from-DB instead of delete-and-raise). Changes correctly reflect the intentional behavioral shift from fail-open deletion to fail-safe reseeding.
tests/test_litellm/proxy/test_proxy_server.py Test updated to verify the new reseed-to-DB-floor behavior (counter becomes 0.6, not 0.25 direct-increment). Legitimate behavioral change, not a weakened assertion.
tests/test_litellm/proxy/auth/test_auth_checks.py All mock get_current_spend signatures updated to accept max_budget=None, **kwargs so they stay compatible with the new call-site signature. No assertion logic changed.
tests/test_litellm/proxy/auth/test_custom_auth_end_user_budget.py Mock signature updated to accept the new max_budget kwarg. No logic changes.
tests/test_litellm/proxy/auth/test_multi_budget_windows.py Mock signatures updated. No assertion changes.
tests/test_litellm/proxy/test_litellm_pre_call_utils.py Mock signatures updated to accept new kwargs. No assertion changes.

Reviews (7): Last reviewed commit: "fix(proxy): enforce budgets against auth..." | Re-trigger Greptile

Comment thread litellm/proxy/proxy_server.py Outdated
Comment thread litellm/proxy/proxy_server.py Outdated
@yassin-berriai
yassin-berriai force-pushed the litellm_enforce_budget_against_stale_counter branch from 81b29cc to a125c19 Compare June 17, 2026 20:36
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Good catch on the per-request DB read. Addressed in the latest commit: the authoritative re-check now caches the DB spend in-process for a short window (SPEND_DB_FLOOR_CACHE_TTL_SECONDS, 5s) via _authoritative_floor_spend, so a persistently stale counter (or a key whose auth-cache spend lags a reset for the 60s auth-cache TTL) drives at most one DB read per counter per window instead of one per request, and concurrent requests on a pod share that cached value. I kept the read-time floor rather than reseeding the Redis counter on purpose: an INCRBYFLOAT-based heal has no cross-pod coordination and can double-apply across replicas, which would over-count and over-block; the bounded read keeps enforcement correct without that risk. Added test_get_current_spend_floor_caches_db_read to pin the single-read behavior.

@greptileai

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

The caching approach in _authoritative_floor_spend is sound. A few observations on the implementation:

  1. Cache key namespace is cleanspend_db_floor:{counter_key} shares the same in_memory_cache as the spend counter but the prefix prevents collision.

  2. Asyncio race is benign — two concurrent coroutines can both see cached is None before the first await from_db completes and both make the DB call. In asyncio's single-threaded model this is bounded (not unbounded like with threads) and both calls return the same authoritative value, so the extra read is harmless. Worth a comment if you want, but not a bug.

  3. Post-reset false-positive window — if the floor cache holds a stale-high value (set just before a budget reset fires), requests can be blocked for up to 5s after the counter resets to 0. The test test_get_current_spend_floor_admits_after_reset covers the case where the cache is cold at reset time, but not the warm-cache path. That 5s window is an acceptable trade-off and you've documented it, just worth being explicit about in the comments.

  4. fallback_spend as the staleness signal — using the auth-object spend (from the 60s auth cache) as the floor heuristic is correct and avoids a DB read for healthy counters. The only edge: if fallback_spend is also stale-low for some independent reason (e.g., a bug in the auth cache), the guard could silently pass. But that's outside the scope of this fix and fallback_spend still acts as the reseed baseline in the existing _read_spend_counter_estimate path, so it's no worse than before.

The logic is correct, the hot path stays off the DB for healthy keys, and the new test pins the single-read guarantee. LGTM.

@yassin-berriai
yassin-berriai force-pushed the litellm_enforce_budget_against_stale_counter branch from a125c19 to 66e398f Compare June 17, 2026 20:39
Comment thread litellm/proxy/proxy_server.py Outdated
@veria-ai

veria-ai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

PR overview

This pull request updates the proxy budget-enforcement path so cross-pod spend counters are checked against a more authoritative database-backed view when cached or Redis-backed spend data may be stale.

One budget-enforcement gap remains open: if Redis has a stale spend value that is below the configured budget but above an older cached fallback value, the code can skip the authoritative database floor and allow an already over-budget user to continue making LLM requests. That creates a bounded budget-bypass/resource-consumption risk until the counter is repaired or refreshed. Three prior issues have already been addressed, so the remaining risk is narrowed to this stale-counter edge case.

Open issues (1)

Fixed/addressed: 3 · PR risk: 6/10

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review the latest commit and post an updated confidence score; the per-request DB read concern from the first review was addressed with the in-process floor cache (_authoritative_floor_spend).

@BerriAI BerriAI deleted a comment from greptile-apps Bot Jun 17, 2026
@yassin-berriai
yassin-berriai force-pushed the litellm_enforce_budget_against_stale_counter branch from 66e398f to a895fd2 Compare June 17, 2026 21:37
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Addressed the open issue (stale counter not repaired) in the latest commit.

When the authoritative DB spend comes back higher than the counter, get_current_spend now writes that value back via _repair_stale_spend_counter, so a counter left stale-low by a Redis restart is corrected for every worker rather than only used for the current request's check. This closes the bypass where a worker whose own cached spend is also stale could keep admitting against the stale counter; once any worker repairs it, all workers read the corrected total from Redis.

The repair uses an idempotent SET, not an increment, so concurrent repairs across pods converge to the same DB value instead of stacking, and it only runs when the counter reads strictly below the DB spend, so it can only raise a stale counter toward the recorded spend and never lowers a healthy one. Covered by test_get_current_spend_floors_stale_low_counter_against_db (asserts the write-back) and test_get_current_spend_floor_admits_after_reset (asserts no write when the counter already matches the DB).

Comment thread litellm/proxy/proxy_server.py Outdated
@yassin-berriai
yassin-berriai force-pushed the litellm_enforce_budget_against_stale_counter branch from a895fd2 to 2f907fa Compare June 17, 2026 22:03
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Addressed the remaining open issue (repair could overwrite concurrent increments) in the latest commit.

The counter repair is now monotonic and atomic. Instead of a plain SET, it goes through RedisCache.async_set_max, which runs a single Lua GET/compare/SET: it only writes when the new value is greater than the stored value, so the GET-compare-SET cannot interleave with a concurrent increment, and a slightly-stale repair can neither lower the counter nor clobber an increment that a racing request already applied. The in-memory copy uses the same raise-only guard with no await between the read and the write, so it is atomic within the worker. The repair still only runs when the counter reads strictly below the authoritative DB spend, so it can only move the counter up toward recorded spend.

@yassin-berriai
yassin-berriai force-pushed the litellm_enforce_budget_against_stale_counter branch from 2f907fa to b41734f Compare June 17, 2026 22:12
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review the latest commit (HEAD); the repair was made monotonic/atomic via RedisCache.async_set_max to address the concurrent-increment concern.

Comment thread litellm/proxy/proxy_server.py Outdated
@yassin-berriai
yassin-berriai force-pushed the litellm_enforce_budget_against_stale_counter branch from b41734f to aa835bc Compare June 17, 2026 22:34
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Addressed the remaining open issue (unsupported counter floors) in the latest commit.

The authoritative floor now covers every hard-budget counter type rather than only the DB-row ones:

  • key / user / team / team-member / org: floor against the DB row (unchanged).
  • per-window key/team (spend:key:*:window:*, spend:team:*:window:*): floor against aggregated spend logs via SpendCounterReseed.window_from_spend_logs, with window context (entity type/id and window start) threaded from the multi-budget checks. Bounded by the same in-process cache, and the corrected value is repaired back monotonically.
  • end-user / tag (spend:end_user:*, spend:tag:*): these have no DB row, so enforcement floors against the caller's fallback_spend, which the auth path already loads fresh from the end-user / tag object. No extra DB read, and a stale-low counter can no longer admit them past budget.

So a tag, end-user, or window that is already over budget is rejected after a Redis reload of a stale-low value, the same as the primary counters. Covered by new tests in test_spend_counters.py (...floors_end_user_tag_against_fallback, ...floors_window_against_spend_logs).

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai re-review latest commit please; extended the authoritative floor to end-user, tag, and per-window counters so no hard-budget counter type is bypassed by a stale Redis value.

@yassin-berriai
yassin-berriai force-pushed the litellm_enforce_budget_against_stale_counter branch from aa835bc to c7b91ab Compare June 17, 2026 22:51
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

On the window-counter GROUP BY: this is an accepted trade-off rather than an oversight. Primary counters have a cheap staleness signal (recorded_spend > counter) so healthy traffic skips the DB, but per-window counters have no equivalent cheap signal (their fallback is 0), so closing the same stale-counter bypass for window budgets requires the spend-logs aggregation. It is bounded to at most one aggregation per window counter per 5s per worker by the in-process cache, and window budgets are coarse (hourly/daily/weekly), so the load is low and the staleness is acceptable. Keeping it so per-window budgets get the same stale-counter protection as primary counters; the alternative (no window floor) leaves window budgets bypassable after a Redis reload.

@yassin-berriai
yassin-berriai force-pushed the litellm_enforce_budget_against_stale_counter branch from c7b91ab to 83a05f5 Compare June 18, 2026 01:41
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Pushed two further hardening changes from a customer incident (Redis counter nil/TTL -2, a $100 key reached $218):

  1. Reconcile no longer fails open. The post-call reservation reconcile used to delete the counter and continue when it found the counter missing or an adjustment that would drive it negative (the deletion left counters unenforced after a Redis reload). It now reseeds from the DB's lagging authoritative floor instead of deleting; the monotonic set-max only ever raises a stale-low counter, and the read-time floor converges to the true total as the spend buffer flushes. The pre-call admission resize path deliberately keeps failing closed.

  2. Opt-in general_settings.fail_closed_budget_enforcement (default false): rejects with 503 when spend can be verified against neither Redis nor the DB, instead of admitting on an unverifiable budget. Default behavior unchanged.

@greptileai please re-review the latest commit.

Comment thread litellm/proxy/proxy_server.py Outdated
# spend this caller already knows about. Window counters have no such
# signal (fallback is 0), so they always re-check, bounded by the cache.
is_window = window_start is not None
if fallback_spend > current or is_window:

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.

Medium: Budget bypass when stale Redis is above cached fallback

fallback_spend comes from cached auth/team/user/tag objects, so it can lag the DB independently of the Redis spend counter. If Redis restores 50, the cached token still says 40, and the DB row is already over a 100 budget at 120, this branch skips the DB floor and a user who is already over budget can keep making LLM requests until another path repairs the counter; consult the cached _authoritative_floor_spend whenever an enforced counter is below max_budget, or otherwise use a signal that does not depend on a potentially stale fallback being greater than the Redis value.

@yassin-berriai
yassin-berriai force-pushed the litellm_enforce_budget_against_stale_counter branch 2 times, most recently from cda235e to 52971a5 Compare June 18, 2026 02:13
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Latest commit closes the "stale Redis above cached fallback" gap and adds full live coverage.

The both-stale gap (counter stale-low AND the caller's cached fallback also stale-low, so the cheap fallback_spend > current guard skips the DB re-check) is real; live testing reproduced it on the team path. With fail_closed_budget_enforcement (strict mode) on, the authoritative re-check now runs for every budgeted entity, so a stale-low counter can no longer slip through regardless of the cached fallback; it stays bounded by the in-process cache. Default mode keeps the cheap guard for the hot path. Anyone who needs the hard guarantee enables the flag (which is also what arms the 503 fail-closed behavior).

Every enforcement path is now live-verified on a proxy (key, team, end-user, tag, per-window, Redis fully stopped -> enforced via Postgres, and the fail-closed 503 with Redis+Postgres both down). The matrix and transcripts are in the PR description.

@greptileai please re-review the latest commit.

@mateo-berri mateo-berri left a comment

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.

seems like fail_closed_budget_enforcement needs to be documented. Please re-request review after. Also, just merge internal staging to get the litellm on windows circleci fix to pass required ci

@balcsida balcsida mentioned this pull request Jun 18, 2026
13 tasks
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Note on the remaining Veria item ("budget bypass when the stale Redis counter is above the cached fallback"): this is the narrow race where a worker's Redis counter and its per-pod cached spend are both stale-low at the same time, so the cheap hot-path guard (fallback_spend > current) skips the DB re-check. Closing it unconditionally means running the authoritative DB read on every budgeted request, which is the hot-path cost the review flagged elsewhere; the two are the same tradeoff from opposite sides.

This is handled by design: fail_closed_budget_enforcement (strict mode) makes the authoritative re-check run for every budgeted entity, closing the gap entirely, and also arms the 503-on-unverifiable behavior. Deployments that want the hard guarantee enable that flag (the same flag the budget-safety ask here is about). Default mode keeps the cheap guard so healthy under-budget traffic stays off the DB. Both paths are live-verified and unit-pinned.

…ross-pod counter is unreliable

Budget enforcement reads spend from the cross-pod Redis counter via get_current_spend, which trusted the counter whenever Redis returned a value. A Redis instance that restarts and reloads an older RDB snapshot (the customer's logs repeat "Redis is loading the dataset in memory") comes back with a stale-low counter; that read is a hit, not a clean miss, so the existing DB reseed never ran and a key kept getting admitted even though its recorded spend was already over max_budget. The symptom was recorded spend sitting above the limit while requests kept succeeding.

Read-time enforcement: get_current_spend takes an optional max_budget and, when the counter would admit the request but reads below this caller's last-known recorded spend, re-reads the authoritative spend and enforces against the higher value. The authoritative source depends on the counter: key/team/user/org/team-member read the DB row, per-window budgets aggregate spend logs, and end-user/tag have no DB row so the caller's freshly-loaded recorded spend is used. Healthy primary counters and freshly reset keys stay off the DB path, and the value is cached in-process for a few seconds, so a persistently stale counter drives at most one read per counter per window. When the DB value is higher, the counter is repaired with a monotonic, atomic set-max (RedisCache.async_set_max) so every worker reads the corrected total and a concurrent increment is never clobbered.

Reconcile no longer fails open: when the post-call reservation reconcile found the counter missing or an adjustment that would drive it negative, it deleted the counter and continued (the deletion is what left counters nil/unenforced after a Redis reload). It now reseeds from the DB's lagging authoritative floor instead of deleting; the monotonic set-max can only raise a stale-low counter, and the read-time floor converges to the true total as the spend buffer flushes. The pre-call admission resize path keeps its original fail-closed behavior.

Opt-in strict enforcement: general_settings.fail_closed_budget_enforcement (default False) makes the authoritative re-check run for every budgeted entity (closing the gap where a stale-low counter and a stale-low cached fallback would otherwise both pass the cheap guard), and rejects a request with 503 when the spend backing an admit decision can be verified against neither Redis nor the database. Default behavior is unchanged; the re-check stays bounded by the in-process cache.

Resolves LIT-3772
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

BerriAI/litellm-docs#371

@yassin-berriai
yassin-berriai enabled auto-merge (squash) June 18, 2026 17:00

@mateo-berri mateo-berri left a comment

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.

LGTM; thanks!

@yassin-berriai
yassin-berriai merged commit a8b94b9 into litellm_internal_staging Jun 18, 2026
123 of 124 checks passed
@yassin-berriai
yassin-berriai deleted the litellm_enforce_budget_against_stale_counter branch June 18, 2026 17:35
yuneng-berri pushed a commit that referenced this pull request Jun 18, 2026
…ross-pod counter is unreliable (#30684)

Budget enforcement reads spend from the cross-pod Redis counter via get_current_spend, which trusted the counter whenever Redis returned a value. A Redis instance that restarts and reloads an older RDB snapshot (the customer's logs repeat "Redis is loading the dataset in memory") comes back with a stale-low counter; that read is a hit, not a clean miss, so the existing DB reseed never ran and a key kept getting admitted even though its recorded spend was already over max_budget. The symptom was recorded spend sitting above the limit while requests kept succeeding.

Read-time enforcement: get_current_spend takes an optional max_budget and, when the counter would admit the request but reads below this caller's last-known recorded spend, re-reads the authoritative spend and enforces against the higher value. The authoritative source depends on the counter: key/team/user/org/team-member read the DB row, per-window budgets aggregate spend logs, and end-user/tag have no DB row so the caller's freshly-loaded recorded spend is used. Healthy primary counters and freshly reset keys stay off the DB path, and the value is cached in-process for a few seconds, so a persistently stale counter drives at most one read per counter per window. When the DB value is higher, the counter is repaired with a monotonic, atomic set-max (RedisCache.async_set_max) so every worker reads the corrected total and a concurrent increment is never clobbered.

Reconcile no longer fails open: when the post-call reservation reconcile found the counter missing or an adjustment that would drive it negative, it deleted the counter and continued (the deletion is what left counters nil/unenforced after a Redis reload). It now reseeds from the DB's lagging authoritative floor instead of deleting; the monotonic set-max can only raise a stale-low counter, and the read-time floor converges to the true total as the spend buffer flushes. The pre-call admission resize path keeps its original fail-closed behavior.

Opt-in strict enforcement: general_settings.fail_closed_budget_enforcement (default False) makes the authoritative re-check run for every budgeted entity (closing the gap where a stale-low counter and a stale-low cached fallback would otherwise both pass the cheap guard), and rejects a request with 503 when the spend backing an admit decision can be verified against neither Redis nor the database. Default behavior is unchanged; the re-check stays bounded by the in-process cache.

Resolves LIT-3772

(cherry picked from commit a8b94b9)
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…ross-pod counter is unreliable (BerriAI#30684)

Budget enforcement reads spend from the cross-pod Redis counter via get_current_spend, which trusted the counter whenever Redis returned a value. A Redis instance that restarts and reloads an older RDB snapshot (the customer's logs repeat "Redis is loading the dataset in memory") comes back with a stale-low counter; that read is a hit, not a clean miss, so the existing DB reseed never ran and a key kept getting admitted even though its recorded spend was already over max_budget. The symptom was recorded spend sitting above the limit while requests kept succeeding.

Read-time enforcement: get_current_spend takes an optional max_budget and, when the counter would admit the request but reads below this caller's last-known recorded spend, re-reads the authoritative spend and enforces against the higher value. The authoritative source depends on the counter: key/team/user/org/team-member read the DB row, per-window budgets aggregate spend logs, and end-user/tag have no DB row so the caller's freshly-loaded recorded spend is used. Healthy primary counters and freshly reset keys stay off the DB path, and the value is cached in-process for a few seconds, so a persistently stale counter drives at most one read per counter per window. When the DB value is higher, the counter is repaired with a monotonic, atomic set-max (RedisCache.async_set_max) so every worker reads the corrected total and a concurrent increment is never clobbered.

Reconcile no longer fails open: when the post-call reservation reconcile found the counter missing or an adjustment that would drive it negative, it deleted the counter and continued (the deletion is what left counters nil/unenforced after a Redis reload). It now reseeds from the DB's lagging authoritative floor instead of deleting; the monotonic set-max can only raise a stale-low counter, and the read-time floor converges to the true total as the spend buffer flushes. The pre-call admission resize path keeps its original fail-closed behavior.

Opt-in strict enforcement: general_settings.fail_closed_budget_enforcement (default False) makes the authoritative re-check run for every budgeted entity (closing the gap where a stale-low counter and a stale-low cached fallback would otherwise both pass the cheap guard), and rejects a request with 503 when the spend backing an admit decision can be verified against neither Redis nor the database. Default behavior is unchanged; the re-check stays bounded by the in-process cache.

Resolves LIT-3772
blake-hamm added a commit to blake-hamm/bhamm-lab that referenced this pull request Jun 28, 2026
…to v1.90.0 (#232)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [https://github.com/BerriAI/litellm.git](https://github.com/BerriAI/litellm) | minor | `v1.89.4` → `v1.90.0` |

---

### Release Notes

<details>
<summary>BerriAI/litellm (https://github.com/BerriAI/litellm.git)</summary>

### [`v1.90.0`](https://github.com/BerriAI/litellm/releases/tag/v1.90.0)

[Compare Source](BerriAI/litellm@v1.89.4...v1.90.0-rc.1)

#### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.90.0
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.90.0/cosign.pub \
  ghcr.io/berriai/litellm:v1.90.0
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

#### What's Changed

- fix(responses-bridge): map system-only chat request to system input item by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29817](BerriAI/litellm#29817)
- feat(bedrock): forward strict and additionalProperties to Converse toolSpec by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29814](BerriAI/litellm#29814)
- fix(mcp): highlight MCP cards red when the logged-in user is missing per-user env vars by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29856](BerriAI/litellm#29856)
- feat(ui): add budget duration to edit team member form by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29717](BerriAI/litellm#29717)
- fix(ui): make workflow runs page fill full width by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29868](BerriAI/litellm#29868)
- feat: standardize rate limit errors with category, rate\_limit\_type, model, and llm\_provider fields by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;27687](BerriAI/litellm#27687)
- fix(ui): default guardrails page to the Guardrails tab by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29872](BerriAI/litellm#29872)
- docs(readme): add Deploy on AWS/GCP Terraform section and fix deploy button rendering by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29879](BerriAI/litellm#29879)
- refactor(bedrock): build Converse toolSpec via a BedrockToolSpec dict subclass by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29869](BerriAI/litellm#29869)
- feat(litellm): add models and repository layers by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29686](BerriAI/litellm#29686)
- feat(ui): include internal routes in the dashboard's generated OpenAPI types by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29885](BerriAI/litellm#29885)
- feat(proxy): publish /v2/model/info in Swagger OpenAPI spec by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29900](BerriAI/litellm#29900)
- refactor(ui): single source of truth for migrated-page routing by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29949](BerriAI/litellm#29949)
- fix(ui/model-hub): render provider icons on the public model hub by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29958](BerriAI/litellm#29958)
- fix(ui): keep create guardrail modal open on outside click by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29871](BerriAI/litellm#29871)
- fix(ui): label default key type as "Full Access" on key edit page by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29870](BerriAI/litellm#29870)
- fix(ui): unify migrated-route URLs and migrate the API Reference page by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29953](BerriAI/litellm#29953)
- fix(mcp): let non-creator users OAuth into OBO-mode MCP servers from the Tools page by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29867](BerriAI/litellm#29867)
- Litellm oss staging 080626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29932](BerriAI/litellm#29932)
- feat(galileo): add health check support for UI callback test by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29908](BerriAI/litellm#29908)
- fix(model-management): allow deleting a BYOK model after its team is deleted by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29875](BerriAI/litellm#29875)
- feat(jwt-auth): opt-in fallback to DB team on unresolved JWT claim by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28913](BerriAI/litellm#28913)
- fix(team\_endpoints): don't block /team/update on unchanged team budget by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29525](BerriAI/litellm#29525)
- fix(fireworks): enable tool calling for glm-5p1 in model cost map by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29697](BerriAI/litellm#29697)
- fix(vertex): propagate Vertex AI metadata in streaming success callbacks by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29899](BerriAI/litellm#29899)
- fix(ui): show team projects to internal users on key creation by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28855](BerriAI/litellm#28855)
- build(deps): bump pyjwt to 2.13.0 and ws override to 8.20.1 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29982](BerriAI/litellm#29982)
- fix(team-management): delete a team's BYOK models when the team is deleted by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29977](BerriAI/litellm#29977)
- feat(vantage): include organization metadata in FOCUS Tags export by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28184](BerriAI/litellm#28184)
- fix(guardrails): read CrowdStrike AIDR identity from both metadata bags by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29991](BerriAI/litellm#29991)
- fix(mcp): mirror upstream token lifetime instead of forcing a 1h OBO expiry by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29951](BerriAI/litellm#29951)
- feat(azure\_ai): add MAI-Image-2.5 image generation support by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29688](BerriAI/litellm#29688)
- fix(mcp): load MCP tool configuration tools via the OBO/passthrough-aware GET path by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29960](BerriAI/litellm#29960)
- fix(team): reserve team budget raises for proxy admins on /team/update by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;30030](BerriAI/litellm#30030)
- test(ui): data-driven App Router migration E2E smoke (default + server-root-path) by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29974](BerriAI/litellm#29974)
- fix(proxy): extend response headers hook to streaming, TTS, image gen, and pass-through by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;24232](BerriAI/litellm#24232)
- chore(ui): remove dead App Router route stubs under (dashboard) by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30045](BerriAI/litellm#30045)
- fix(ui/mcp): reset OAuth state on create-server modal close so a prior server's token no longer leaks into the next add-server session by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;30000](BerriAI/litellm#30000)
- fix(mcp): allow team access-group grants in OAuth authorize/token access check by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;30041](BerriAI/litellm#30041)
- docs(security): require a reproduction video for vulnerability reports by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30063](BerriAI/litellm#30063)
- feat(ui): add admin flag to disable in-product UI nudges for everyone by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29796](BerriAI/litellm#29796)
- chore(ui): remove dead dashboard files and unused dependencies by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30047](BerriAI/litellm#30047)
- fix(proxy): authorize batch files using upload target\_model\_names (LIT-3593) by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30009](BerriAI/litellm#30009)
- Add Claude Fable 5 across Anthropic, Bedrock, Vertex AI, and Azure AI by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30064](BerriAI/litellm#30064)
- Add Claude Fable 5 cost map entries (data-only hotfix for the hosted map) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30076](BerriAI/litellm#30076)
- fix(caching): restore stored prompt\_tokens on embedding cache hits instead of recomputing by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;30046](BerriAI/litellm#30046)
- Litellm oss 090626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30021](BerriAI/litellm#30021)
- fix(proxy): self-heal startup/reload prisma reads on engine disconnect by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;28803](BerriAI/litellm#28803)
- chore(ui): make knip recognize .mjs scripts and openapi-typescript by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30052](BerriAI/litellm#30052)
- fix(register\_model): preserve built-in cache pricing when registering custom overrides under unmapped keys by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30044](BerriAI/litellm#30044)
- \[internal copy of [#&#8203;28007](BerriAI/litellm#28007)] Fix/gcp model garden streaming by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28363](BerriAI/litellm#28363)
- feat(cli): per-agent `lite claude` / `codex` / `opencode` commands that wrap coding agents through the proxy by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29850](BerriAI/litellm#29850)
- fix(callbacks): forward callback\_settings to callback initializers and guard consumers against non-dict values by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30161](BerriAI/litellm#30161)
- fix(mcp): drop orphaned per-user credential rows when an MCP server is deleted by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;30141](BerriAI/litellm#30141)
- fix(proxy): recover from cached-plan errors by reconnecting the Prisma client by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29983](BerriAI/litellm#29983)
- feat(proxy): add option to disable server-side prepared statements for DB lookups by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29984](BerriAI/litellm#29984)
- fix(release): stop backport releases from overwriting the latest badge by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30005](BerriAI/litellm#30005)
- feat: add conventional commits and coding guidelines by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30159](BerriAI/litellm#30159)
- fix(proxy): return 5xx on DB infra errors during auth; reserve 401 for genuine auth failures by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29986](BerriAI/litellm#29986)
- fix(ui): dev server 404s on migrated-page links because uiBase hardcodes /ui by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30169](BerriAI/litellm#30169)
- refactor(ui): consolidate dashboard to one shell in the (dashboard) layout by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30166](BerriAI/litellm#30166)
- fix(proxy): align /v1/model/info with router deployments by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30025](BerriAI/litellm#30025)
- fix: completion\_cost AttributeError on streaming Anthropic web\_search responses ([#&#8203;26153](BerriAI/litellm#26153)) by [@&#8203;ishaan-berri](https://github.com/ishaan-berri) in [#&#8203;27346](BerriAI/litellm#27346)
- \[internal copy of [#&#8203;30137](BerriAI/litellm#30137)] perf(realtime): eliminate redundant per-frame JSON work on OpenAI realtime relay by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30142](BerriAI/litellm#30142)
- feat(bedrock): aws\_bedrock\_project\_id for bedrock-mantle project / workspace association by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30163](BerriAI/litellm#30163)
- chore(hooks): enforce Conventional Commits and Conventional Branches by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30174](BerriAI/litellm#30174)
- feat(rate-limiter): allow opting out of v3 TPM reservation and Redis circuit breaker by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30211](BerriAI/litellm#30211)
- feat(spend\_logs): opt-in native Postgres partitioning for SpendLogs retention by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29466](BerriAI/litellm#29466)
- feat(ui): migrate playground to path routing and colocate its files by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30185](BerriAI/litellm#30185)
- feat(ui): migrate projects and access-groups to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30226](BerriAI/litellm#30226)
- fix(proxy): coalesce NULL rollup metrics in aggregated daily-activity by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;30151](BerriAI/litellm#30151)
- fix(anthropic\_passthrough): resolve costing model from message\_start chunk, litellm\_params and model\_group instead of 'unknown' by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30160](BerriAI/litellm#30160)
- feat(ui): migrate budgets, workflows, and guardrails-monitor to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30236](BerriAI/litellm#30236)
- feat(ui): migrate mcp-servers, search-tools, tag-management, vector-stores, and memory to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30261](BerriAI/litellm#30261)
- fix(a2a): forward agent\_extra\_headers through completion bridge by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28277](BerriAI/litellm#28277)
- fix(gemini-live): forward audio buffer commit and correct Vertex PCM rate by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29946](BerriAI/litellm#29946)
- fix(proxy): skip double-wrapping unified batch output file ids on retrieve by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30011](BerriAI/litellm#30011)
- feat: litellm oss 110626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30202](BerriAI/litellm#30202)
- fix(docker): copy only runtime artifacts into the final image by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30243](BerriAI/litellm#30243)
- feat(proxy): enforce key/team guardrails on bedrock passthrough routes by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30194](BerriAI/litellm#30194)
- feat(gemini): forward web search tools in image generation by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30119](BerriAI/litellm#30119)
- fix: bedrock mantle fixes by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30083](BerriAI/litellm#30083)
- feat(proxy): add require\_managed\_files setting for file uploads by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30186](BerriAI/litellm#30186)
- fix(mcp): honor server\_id for REST tool calls with shared upstream URLs by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30184](BerriAI/litellm#30184)
- fix(responses): presidio PII masking for Azure WebSocket and streaming by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30003](BerriAI/litellm#30003)
- feat(passthrough): add configurable pass-through request timeouts by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30266](BerriAI/litellm#30266)
- fix(google\_genai): preserve complete SSE events in Vertex/Gemini image streaming by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30270](BerriAI/litellm#30270)
- fix(proxy): populate access\_via\_team\_ids on /v1/model/info by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30274](BerriAI/litellm#30274)
- chore(oss): litellm oss staging 120626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30292](BerriAI/litellm#30292)
- feat(ui): migrate policies, guardrails, prompts, tool-policies, and skills to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30263](BerriAI/litellm#30263)
- feat(ui): migrate caching, cost-tracking, transform-request, ui-theme, and logs to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30267](BerriAI/litellm#30267)
- fix(ui): gate dashboard layout on ui config load so deep links work under SERVER\_ROOT\_PATH by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30312](BerriAI/litellm#30312)
- feat(ui): migrate admin-panel, logging-and-alerts, model-hub-table, and usage to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30268](BerriAI/litellm#30268)
- fix(otel): cap metric attribute cardinality with include/exclude lists by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30257](BerriAI/litellm#30257)
- fix(proxy): grace-period key rotation 401s; return deprecated-key lookup result directly by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30327](BerriAI/litellm#30327)
- chore(deps): bump vitest, brace-expansion, pypdf and tornado by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30220](BerriAI/litellm#30220)
- refactor(ui): remove unreachable /chat page by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30178](BerriAI/litellm#30178)
- feat(ui): migrate agents and router-settings to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30323](BerriAI/litellm#30323)
- feat: strengthen coding conventions in CLAUDE.md by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30333](BerriAI/litellm#30333)
- feat(ui): cut the users page over to the /ui/users path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30334](BerriAI/litellm#30334)
- feat: ruff strict-rule suppressions baseline gate by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30303](BerriAI/litellm#30303)
- feat(guardrails): add Cisco AI Defense integration ([#&#8203;28249](BerriAI/litellm#28249)) by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30338](BerriAI/litellm#30338)
- chore(ui): remove dead UI components unreferenced by any page by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30340](BerriAI/litellm#30340)
- ci: add osv-scanner lockfile scan workflow by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30222](BerriAI/litellm#30222)
- fix(otel): record full error message on standard exception event in otel v2 by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30380](BerriAI/litellm#30380)
- test(fireworks): mock whisper transcription tests instead of live calls by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30391](BerriAI/litellm#30391)
- build(ui): pin esbuild to 0.28.1 via overrides by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30390](BerriAI/litellm#30390)
- feat(ui): cut the organizations page over to the /ui/organizations path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30336](BerriAI/litellm#30336)
- fix(proxy): support SMTP implicit SSL (port 465) by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30395](BerriAI/litellm#30395)
- fix(mcp): default Linear MCP registry entry to streamable HTTP by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30396](BerriAI/litellm#30396)
- fix(ui): stop Virtual Keys page from infinite render loop by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30397](BerriAI/litellm#30397)
- fix(streaming): guard raise\_on\_model\_repetition against empty choices by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;30485](BerriAI/litellm#30485)
- feat(otel-v2): emit the 6 gen\_ai.client.\* metrics at parity with v1 by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30326](BerriAI/litellm#30326)
- fix(mcp): drop phantom 401 span on delegated OAuth2 tool calls by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30494](BerriAI/litellm#30494)
- feat(ui): cut the teams page over to the /ui/teams path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30343](BerriAI/litellm#30343)
- fix(integrations): cap Anthropic cache\_control injection at 4 blocks by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;30480](BerriAI/litellm#30480)
- chore(codecov): add Batches, Videos, and Realtime components by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30517](BerriAI/litellm#30517)
- test(batches): move orphan tests into tests/test\_litellm for CI coverage by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30510](BerriAI/litellm#30510)
- fix(guardrails): run pre\_call hook once for model-level guardrails by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30543](BerriAI/litellm#30543)
- fix(guardrails): stop re-initializing DB guardrails on every poll by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30542](BerriAI/litellm#30542)
- chore(oss): litellm oss staging 150626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30463](BerriAI/litellm#30463)
- ci(lint): add blanket-noqa, dataclass-default, and unused-noqa Ruff rules by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30516](BerriAI/litellm#30516)
- ci: ratchet lint and type-check gates (ruff preview, ANN, mypy, basedpyright) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30379](BerriAI/litellm#30379)
- fix(proxy): allow internal roles to access vector store CRUD routes by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;30503](BerriAI/litellm#30503)
- fix(otel): stamp gen\_ai.input/output.messages on v2 spans by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30548](BerriAI/litellm#30548)
- fix(otel): export v2 gen\_ai client metrics to the configured meter provider by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30549](BerriAI/litellm#30549)
- fix(bedrock): preserve cache\_control for ARN models in /v1/messages adapter by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29823](BerriAI/litellm#29823)
- fix: greatly increase basedpyright slack by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30563](BerriAI/litellm#30563)
- fix(budget): recompute budget\_reset\_at when budget\_duration changes on /budget/update by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30555](BerriAI/litellm#30555)
- fix(otel): accept UPPER\_SNAKE\_CASE OTEL\_INSTRUMENTATION\_GENAI\_CAPTURE\_MESSAGE\_CONTENT in v2 by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30562](BerriAI/litellm#30562)
- chore(lint): remove PLR0915 too-many-statements ruff rule by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30574](BerriAI/litellm#30574)
- ci(lint): ratcheted type-discipline gate (mutable collections, casts, guards, kwargs, suppressions) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30500](BerriAI/litellm#30500)
- feat(proxy): add verification\_uri\_complete to CLI SSO device flow by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30571](BerriAI/litellm#30571)
- chore: litellm oss staging160626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30527](BerriAI/litellm#30527)
- fix(guardrails): return 400 not 500 when AIM blocks a request by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30573](BerriAI/litellm#30573)
- ci(lint): grandfather any-discipline with a per-file ratchet budget (50% headroom) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30582](BerriAI/litellm#30582)
- fix(audio): don't override explicit response\_format with verbose\_json by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30599](BerriAI/litellm#30599)
- fix(anthropic): price and surface response service\_tier in cost tracking by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30558](BerriAI/litellm#30558)
- feat: add dev and wildcard proxy configs for local testing by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30556](BerriAI/litellm#30556)
- fix(proxy): list public team model name in /v1/models by [@&#8203;ishaan-berri](https://github.com/ishaan-berri) in [#&#8203;30588](BerriAI/litellm#30588)
- ci: drop mypy entirely, standardize type checking on basedpyright by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30648](BerriAI/litellm#30648)
- feat(guardrails): surface OpenAI moderation violation\_categories on guardrail traces by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30659](BerriAI/litellm#30659)
- fix(proxy): resolve list files credentials from team BYOK deployments by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;30495](BerriAI/litellm#30495)
- feat(proxy): add --max\_requests\_before\_restart\_jitter to stagger worker restarts by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30601](BerriAI/litellm#30601)
- fix(health): correct bedrock embedding health checks by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30583](BerriAI/litellm#30583)
- test: harden remaining pass-through CI flakes (image-gen spend poll, ruby assistants timeout) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30685](BerriAI/litellm#30685)
- test(pass\_through): harden vertex spendlog poll against transient empty reads by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30683](BerriAI/litellm#30683)
- fix(cost): stop non-string service\_tier from silently dropping cost tracking by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30690](BerriAI/litellm#30690)
- feat(proxy): warn at startup when custom\_auth skips common\_checks enforcement by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;30665](BerriAI/litellm#30665)
- fix(pod\_lock): release cron lock by matching async\_set\_cache JSON encoding by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30600](BerriAI/litellm#30600)
- ci: run a local fake OpenAI endpoint instead of the shared Railway mock by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30695](BerriAI/litellm#30695)
- ci(windows): pin uv to Python 3.11 so it ignores the preinstalled 3.14 by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30704](BerriAI/litellm#30704)
- feat(ui): migrate models page to App Router path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30677](BerriAI/litellm#30677)
- refactor(ui): remove orphaned pass-through-settings route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30692](BerriAI/litellm#30692)
- fix(cost): stop non-string response service\_tier from dropping cost tracking by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30706](BerriAI/litellm#30706)
- feat(agent-shin): automated PR/issue triage, low-quality auto-close, and review-gate label lifecycle by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30433](BerriAI/litellm#30433)
- chore: litellm oss 170626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30637](BerriAI/litellm#30637)
- fix(bedrock\_mantle): add SigV4 fallback to chat completions auth by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30714](BerriAI/litellm#30714)
- feat(search): add TinyFish as search provider by [@&#8203;simantak-dabhade](https://github.com/simantak-dabhade) in [#&#8203;30634](BerriAI/litellm#30634)
- feat(ui): migrate old usage report to App Router path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30694](BerriAI/litellm#30694)
- fix(proxy): enforce budgets against authoritative DB spend when the cross-pod counter is stale by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30684](BerriAI/litellm#30684)
- chore(ci): remove Agent Shin pull\_request\_target workflows by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30784](BerriAI/litellm#30784)
- chore: litellm oss staging by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30745](BerriAI/litellm#30745)
- ci(zizmor): also run on litellm\_internal\_staging by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30789](BerriAI/litellm#30789)
- fix(test): drop references to removed Agent Shin workflows by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30791](https://github.com/BerriAI/litellm/pull/30791)
- chore: remove in-product survey and Claude Code feedback nudges by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30773](https://github.com/BerriAI/litellm/pull/30773)
- feat(ui): migrate api-keys landing to App Router path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30699](https://github.com/BerriAI/litellm/pull/30699)
- feat(proxy): configurable response headers and login-page hint by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;30792](https://github.com/BerriAI/litellm/pull/30792)
- ci(zizmor): gate PRs on medium+ findings and clear existing ones by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30797](https://github.com/BerriAI/litellm/pull/30797)
- fix(proxy): use e.request\_data for logging\_obj in ModifyResponseException streaming passthrough by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30800](https://github.com/BerriAI/litellm/pull/30800)
- chore: make pr template linear portion clearer by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30766](https://github.com/BerriAI/litellm/pull/30766)
- chore(typing): add boto3/botocore stubs so basedpyright resolves the AWS SDK by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30815](https://github.com/BerriAI/litellm/pull/30815)
- fix(otel): one v2 logger owns the global provider; scope tenant OTLP creds per exporter by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;30590](https://github.com/BerriAI/litellm/pull/30590)
- fix(passthrough): recover output tokens for interrupted anthropic streams by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30787](https://github.com/BerriAI/litellm/pull/30787)
- fix(proxy): record partial spend on the failure row for interrupted streams by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30788](https://github.com/BerriAI/litellm/pull/30788)
- fix(ui): repoint dead usage guide link to cost tracking docs by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30859](https://github.com/BerriAI/litellm/pull/30859)
- fix(ui): warn that team models are deleted in the delete-team modal by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29990](https://github.com/BerriAI/litellm/pull/29990)
- feat(caching): add valkey-semantic cache backend and fix semantic cache scope keys by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30675](https://github.com/BerriAI/litellm/pull/30675)
- test(ui): isolate OldTeams delete-warning tests from leaked mock by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30871](https://github.com/BerriAI/litellm/pull/30871)
- feat: add lint-gate target and truncation-proof summary to the strict ruff gate by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30877](https://github.com/BerriAI/litellm/pull/30877)
- chore(ui): rebuild ui for release by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30894](https://github.com/BerriAI/litellm/pull/30894)
- chore(ci): bump deps by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30899](https://github.com/BerriAI/litellm/pull/30899)
- fix(watsonx): wrap string embedding input in array for WatsonX API by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;30897](https://github.com/BerriAI/litellm/pull/30897)
- test: point router/completion/triton tests at the local fake OpenAI endpoint by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30900](https://github.com/BerriAI/litellm/pull/30900)
- feat(sandbox): e2b code execution primitive by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;30898](https://github.com/BerriAI/litellm/pull/30898)
- fix(ui): source api-keys identity from useAuthorized to stop "User ID is not set" by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30903](https://github.com/BerriAI/litellm/pull/30903)
- chore(ui): rebuild ui by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30906](https://github.com/BerriAI/litellm/pull/30906)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30907](https://github.com/BerriAI/litellm/pull/30907)
- fix(redis): prevent forcing SSLConnection when ssl=False in connection pool by [@&#8203;Jacopos311](https://github.com/Jacopos311) in [#&#8203;30770](https://github.com/BerriAI/litellm/pull/30770)
- fix(proxy): log UI setup failures instead of silently swallowing by [@&#8203;sarvesh1327](https://github.com/sarvesh1327) in [#&#8203;30819](https://github.com/BerriAI/litellm/pull/30819)

#### New Contributors

- [@&#8203;simantak-dabhade](https://github.com/simantak-dabhade) made their first contribution in [#&#8203;30634](BerriAI/litellm#30634)
- [@&#8203;Jacopos311](https://github.com/Jacopos311) made their first contribution in [#&#8203;30770](https://github.com/BerriAI/litellm/pull/30770)
- [@&#8203;sarvesh1327](https://github.com/sarvesh1327) made their first contribution in [#&#8203;30819](https://github.com/BerriAI/litellm/pull/30819)

**Full Changelog**: <BerriAI/litellm@v1.89.0...v1.90.0>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMjAuMCIsInVwZGF0ZWRJblZlciI6IjQzLjIyMC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: Renovate Bot <renovate@bhamm-lab.com>
Reviewed-on: https://codeberg.org/blake-hamm/bhamm-lab/pulls/232
blake-hamm added a commit to blake-hamm/bhamm-lab that referenced this pull request Jun 28, 2026
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | final | minor | `v1.85.1` → `v1.90.0` |

---

### Release Notes

<details>
<summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary>

### [`v1.90.0`](https://github.com/BerriAI/litellm/releases/tag/v1.90.0)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.90.0...v1.90.0)

##### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.90.0
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.90.0/cosign.pub \
  ghcr.io/berriai/litellm:v1.90.0
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

##### What's Changed

- fix(responses-bridge): map system-only chat request to system input item by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29817](https://github.com/BerriAI/litellm/pull/29817)
- feat(bedrock): forward strict and additionalProperties to Converse toolSpec by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29814](https://github.com/BerriAI/litellm/pull/29814)
- fix(mcp): highlight MCP cards red when the logged-in user is missing per-user env vars by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29856](https://github.com/BerriAI/litellm/pull/29856)
- feat(ui): add budget duration to edit team member form by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29717](https://github.com/BerriAI/litellm/pull/29717)
- fix(ui): make workflow runs page fill full width by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29868](https://github.com/BerriAI/litellm/pull/29868)
- feat: standardize rate limit errors with category, rate\_limit\_type, model, and llm\_provider fields by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;27687](https://github.com/BerriAI/litellm/pull/27687)
- fix(ui): default guardrails page to the Guardrails tab by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29872](https://github.com/BerriAI/litellm/pull/29872)
- docs(readme): add Deploy on AWS/GCP Terraform section and fix deploy button rendering by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29879](https://github.com/BerriAI/litellm/pull/29879)
- refactor(bedrock): build Converse toolSpec via a BedrockToolSpec dict subclass by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29869](https://github.com/BerriAI/litellm/pull/29869)
- feat(litellm): add models and repository layers by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29686](https://github.com/BerriAI/litellm/pull/29686)
- feat(ui): include internal routes in the dashboard's generated OpenAPI types by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29885](https://github.com/BerriAI/litellm/pull/29885)
- feat(proxy): publish /v2/model/info in Swagger OpenAPI spec by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29900](https://github.com/BerriAI/litellm/pull/29900)
- refactor(ui): single source of truth for migrated-page routing by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29949](https://github.com/BerriAI/litellm/pull/29949)
- fix(ui/model-hub): render provider icons on the public model hub by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29958](https://github.com/BerriAI/litellm/pull/29958)
- fix(ui): keep create guardrail modal open on outside click by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29871](https://github.com/BerriAI/litellm/pull/29871)
- fix(ui): label default key type as "Full Access" on key edit page by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29870](https://github.com/BerriAI/litellm/pull/29870)
- fix(ui): unify migrated-route URLs and migrate the API Reference page by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29953](https://github.com/BerriAI/litellm/pull/29953)
- fix(mcp): let non-creator users OAuth into OBO-mode MCP servers from the Tools page by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29867](https://github.com/BerriAI/litellm/pull/29867)
- Litellm oss staging 080626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29932](https://github.com/BerriAI/litellm/pull/29932)
- feat(galileo): add health check support for UI callback test by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29908](https://github.com/BerriAI/litellm/pull/29908)
- fix(model-management): allow deleting a BYOK model after its team is deleted by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29875](https://github.com/BerriAI/litellm/pull/29875)
- feat(jwt-auth): opt-in fallback to DB team on unresolved JWT claim by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28913](https://github.com/BerriAI/litellm/pull/28913)
- fix(team\_endpoints): don't block /team/update on unchanged team budget by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29525](https://github.com/BerriAI/litellm/pull/29525)
- fix(fireworks): enable tool calling for glm-5p1 in model cost map by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29697](https://github.com/BerriAI/litellm/pull/29697)
- fix(vertex): propagate Vertex AI metadata in streaming success callbacks by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29899](https://github.com/BerriAI/litellm/pull/29899)
- fix(ui): show team projects to internal users on key creation by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28855](https://github.com/BerriAI/litellm/pull/28855)
- build(deps): bump pyjwt to 2.13.0 and ws override to 8.20.1 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29982](https://github.com/BerriAI/litellm/pull/29982)
- fix(team-management): delete a team's BYOK models when the team is deleted by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29977](https://github.com/BerriAI/litellm/pull/29977)
- feat(vantage): include organization metadata in FOCUS Tags export by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28184](https://github.com/BerriAI/litellm/pull/28184)
- fix(guardrails): read CrowdStrike AIDR identity from both metadata bags by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29991](https://github.com/BerriAI/litellm/pull/29991)
- fix(mcp): mirror upstream token lifetime instead of forcing a 1h OBO expiry by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29951](https://github.com/BerriAI/litellm/pull/29951)
- feat(azure\_ai): add MAI-Image-2.5 image generation support by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29688](https://github.com/BerriAI/litellm/pull/29688)
- fix(mcp): load MCP tool configuration tools via the OBO/passthrough-aware GET path by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29960](https://github.com/BerriAI/litellm/pull/29960)
- fix(team): reserve team budget raises for proxy admins on /team/update by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;30030](https://github.com/BerriAI/litellm/pull/30030)
- test(ui): data-driven App Router migration E2E smoke (default + server-root-path) by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29974](https://github.com/BerriAI/litellm/pull/29974)
- fix(proxy): extend response headers hook to streaming, TTS, image gen, and pass-through by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;24232](https://github.com/BerriAI/litellm/pull/24232)
- chore(ui): remove dead App Router route stubs under (dashboard) by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30045](https://github.com/BerriAI/litellm/pull/30045)
- fix(ui/mcp): reset OAuth state on create-server modal close so a prior server's token no longer leaks into the next add-server session by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;30000](https://github.com/BerriAI/litellm/pull/30000)
- fix(mcp): allow team access-group grants in OAuth authorize/token access check by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;30041](https://github.com/BerriAI/litellm/pull/30041)
- docs(security): require a reproduction video for vulnerability reports by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30063](https://github.com/BerriAI/litellm/pull/30063)
- feat(ui): add admin flag to disable in-product UI nudges for everyone by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29796](https://github.com/BerriAI/litellm/pull/29796)
- chore(ui): remove dead dashboard files and unused dependencies by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30047](https://github.com/BerriAI/litellm/pull/30047)
- fix(proxy): authorize batch files using upload target\_model\_names (LIT-3593) by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30009](https://github.com/BerriAI/litellm/pull/30009)
- Add Claude Fable 5 across Anthropic, Bedrock, Vertex AI, and Azure AI by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30064](https://github.com/BerriAI/litellm/pull/30064)
- Add Claude Fable 5 cost map entries (data-only hotfix for the hosted map) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30076](https://github.com/BerriAI/litellm/pull/30076)
- fix(caching): restore stored prompt\_tokens on embedding cache hits instead of recomputing by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;30046](https://github.com/BerriAI/litellm/pull/30046)
- Litellm oss 090626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30021](https://github.com/BerriAI/litellm/pull/30021)
- fix(proxy): self-heal startup/reload prisma reads on engine disconnect by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;28803](https://github.com/BerriAI/litellm/pull/28803)
- chore(ui): make knip recognize .mjs scripts and openapi-typescript by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30052](https://github.com/BerriAI/litellm/pull/30052)
- fix(register\_model): preserve built-in cache pricing when registering custom overrides under unmapped keys by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30044](https://github.com/BerriAI/litellm/pull/30044)
- \[internal copy of [#&#8203;28007](https://github.com/BerriAI/litellm/issues/28007)] Fix/gcp model garden streaming by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28363](https://github.com/BerriAI/litellm/pull/28363)
- feat(cli): per-agent `lite claude` / `codex` / `opencode` commands that wrap coding agents through the proxy by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29850](https://github.com/BerriAI/litellm/pull/29850)
- fix(callbacks): forward callback\_settings to callback initializers and guard consumers against non-dict values by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30161](https://github.com/BerriAI/litellm/pull/30161)
- fix(mcp): drop orphaned per-user credential rows when an MCP server is deleted by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;30141](https://github.com/BerriAI/litellm/pull/30141)
- fix(proxy): recover from cached-plan errors by reconnecting the Prisma client by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29983](https://github.com/BerriAI/litellm/pull/29983)
- feat(proxy): add option to disable server-side prepared statements for DB lookups by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29984](https://github.com/BerriAI/litellm/pull/29984)
- fix(release): stop backport releases from overwriting the latest badge by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30005](https://github.com/BerriAI/litellm/pull/30005)
- feat: add conventional commits and coding guidelines by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30159](https://github.com/BerriAI/litellm/pull/30159)
- fix(proxy): return 5xx on DB infra errors during auth; reserve 401 for genuine auth failures by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29986](https://github.com/BerriAI/litellm/pull/29986)
- fix(ui): dev server 404s on migrated-page links because uiBase hardcodes /ui by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30169](https://github.com/BerriAI/litellm/pull/30169)
- refactor(ui): consolidate dashboard to one shell in the (dashboard) layout by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30166](https://github.com/BerriAI/litellm/pull/30166)
- fix(proxy): align /v1/model/info with router deployments by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30025](https://github.com/BerriAI/litellm/pull/30025)
- fix: completion\_cost AttributeError on streaming Anthropic web\_search responses ([#&#8203;26153](https://github.com/BerriAI/litellm/issues/26153)) by [@&#8203;ishaan-berri](https://github.com/ishaan-berri) in [#&#8203;27346](https://github.com/BerriAI/litellm/pull/27346)
- \[internal copy of [#&#8203;30137](https://github.com/BerriAI/litellm/issues/30137)] perf(realtime): eliminate redundant per-frame JSON work on OpenAI realtime relay by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30142](https://github.com/BerriAI/litellm/pull/30142)
- feat(bedrock): aws\_bedrock\_project\_id for bedrock-mantle project / workspace association by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30163](https://github.com/BerriAI/litellm/pull/30163)
- chore(hooks): enforce Conventional Commits and Conventional Branches by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30174](https://github.com/BerriAI/litellm/pull/30174)
- feat(rate-limiter): allow opting out of v3 TPM reservation and Redis circuit breaker by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30211](https://github.com/BerriAI/litellm/pull/30211)
- feat(spend\_logs): opt-in native Postgres partitioning for SpendLogs retention by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29466](https://github.com/BerriAI/litellm/pull/29466)
- feat(ui): migrate playground to path routing and colocate its files by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30185](https://github.com/BerriAI/litellm/pull/30185)
- feat(ui): migrate projects and access-groups to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30226](https://github.com/BerriAI/litellm/pull/30226)
- fix(proxy): coalesce NULL rollup metrics in aggregated daily-activity by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;30151](https://github.com/BerriAI/litellm/pull/30151)
- fix(anthropic\_passthrough): resolve costing model from message\_start chunk, litellm\_params and model\_group instead of 'unknown' by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30160](https://github.com/BerriAI/litellm/pull/30160)
- feat(ui): migrate budgets, workflows, and guardrails-monitor to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30236](https://github.com/BerriAI/litellm/pull/30236)
- feat(ui): migrate mcp-servers, search-tools, tag-management, vector-stores, and memory to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30261](https://github.com/BerriAI/litellm/pull/30261)
- fix(a2a): forward agent\_extra\_headers through completion bridge by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28277](https://github.com/BerriAI/litellm/pull/28277)
- fix(gemini-live): forward audio buffer commit and correct Vertex PCM rate by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29946](https://github.com/BerriAI/litellm/pull/29946)
- fix(proxy): skip double-wrapping unified batch output file ids on retrieve by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30011](https://github.com/BerriAI/litellm/pull/30011)
- feat: litellm oss 110626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30202](https://github.com/BerriAI/litellm/pull/30202)
- fix(docker): copy only runtime artifacts into the final image by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30243](https://github.com/BerriAI/litellm/pull/30243)
- feat(proxy): enforce key/team guardrails on bedrock passthrough routes by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30194](https://github.com/BerriAI/litellm/pull/30194)
- feat(gemini): forward web search tools in image generation by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30119](https://github.com/BerriAI/litellm/pull/30119)
- fix: bedrock mantle fixes by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30083](https://github.com/BerriAI/litellm/pull/30083)
- feat(proxy): add require\_managed\_files setting for file uploads by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30186](https://github.com/BerriAI/litellm/pull/30186)
- fix(mcp): honor server\_id for REST tool calls with shared upstream URLs by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30184](https://github.com/BerriAI/litellm/pull/30184)
- fix(responses): presidio PII masking for Azure WebSocket and streaming by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30003](https://github.com/BerriAI/litellm/pull/30003)
- feat(passthrough): add configurable pass-through request timeouts by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30266](https://github.com/BerriAI/litellm/pull/30266)
- fix(google\_genai): preserve complete SSE events in Vertex/Gemini image streaming by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30270](https://github.com/BerriAI/litellm/pull/30270)
- fix(proxy): populate access\_via\_team\_ids on /v1/model/info by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30274](https://github.com/BerriAI/litellm/pull/30274)
- chore(oss): litellm oss staging 120626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30292](https://github.com/BerriAI/litellm/pull/30292)
- feat(ui): migrate policies, guardrails, prompts, tool-policies, and skills to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30263](https://github.com/BerriAI/litellm/pull/30263)
- feat(ui): migrate caching, cost-tracking, transform-request, ui-theme, and logs to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30267](https://github.com/BerriAI/litellm/pull/30267)
- fix(ui): gate dashboard layout on ui config load so deep links work under SERVER\_ROOT\_PATH by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30312](https://github.com/BerriAI/litellm/pull/30312)
- feat(ui): migrate admin-panel, logging-and-alerts, model-hub-table, and usage to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30268](https://github.com/BerriAI/litellm/pull/30268)
- fix(otel): cap metric attribute cardinality with include/exclude lists by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30257](https://github.com/BerriAI/litellm/pull/30257)
- fix(proxy): grace-period key rotation 401s; return deprecated-key lookup result directly by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30327](https://github.com/BerriAI/litellm/pull/30327)
- chore(deps): bump vitest, brace-expansion, pypdf and tornado by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30220](https://github.com/BerriAI/litellm/pull/30220)
- refactor(ui): remove unreachable /chat page by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30178](https://github.com/BerriAI/litellm/pull/30178)
- feat(ui): migrate agents and router-settings to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30323](https://github.com/BerriAI/litellm/pull/30323)
- feat: strengthen coding conventions in CLAUDE.md by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30333](https://github.com/BerriAI/litellm/pull/30333)
- feat(ui): cut the users page over to the /ui/users path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30334](https://github.com/BerriAI/litellm/pull/30334)
- feat: ruff strict-rule suppressions baseline gate by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30303](https://github.com/BerriAI/litellm/pull/30303)
- feat(guardrails): add Cisco AI Defense integration ([#&#8203;28249](https://github.com/BerriAI/litellm/issues/28249)) by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30338](https://github.com/BerriAI/litellm/pull/30338)
- chore(ui): remove dead UI components unreferenced by any page by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30340](https://github.com/BerriAI/litellm/pull/30340)
- ci: add osv-scanner lockfile scan workflow by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30222](https://github.com/BerriAI/litellm/pull/30222)
- fix(otel): record full error message on standard exception event in otel v2 by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30380](https://github.com/BerriAI/litellm/pull/30380)
- test(fireworks): mock whisper transcription tests instead of live calls by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30391](https://github.com/BerriAI/litellm/pull/30391)
- build(ui): pin esbuild to 0.28.1 via overrides by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30390](https://github.com/BerriAI/litellm/pull/30390)
- feat(ui): cut the organizations page over to the /ui/organizations path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30336](https://github.com/BerriAI/litellm/pull/30336)
- fix(proxy): support SMTP implicit SSL (port 465) by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30395](https://github.com/BerriAI/litellm/pull/30395)
- fix(mcp): default Linear MCP registry entry to streamable HTTP by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30396](https://github.com/BerriAI/litellm/pull/30396)
- fix(ui): stop Virtual Keys page from infinite render loop by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30397](https://github.com/BerriAI/litellm/pull/30397)
- fix(streaming): guard raise\_on\_model\_repetition against empty choices by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;30485](https://github.com/BerriAI/litellm/pull/30485)
- feat(otel-v2): emit the 6 gen\_ai.client.\* metrics at parity with v1 by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30326](https://github.com/BerriAI/litellm/pull/30326)
- fix(mcp): drop phantom 401 span on delegated OAuth2 tool calls by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30494](https://github.com/BerriAI/litellm/pull/30494)
- feat(ui): cut the teams page over to the /ui/teams path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30343](https://github.com/BerriAI/litellm/pull/30343)
- fix(integrations): cap Anthropic cache\_control injection at 4 blocks by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;30480](https://github.com/BerriAI/litellm/pull/30480)
- chore(codecov): add Batches, Videos, and Realtime components by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30517](https://github.com/BerriAI/litellm/pull/30517)
- test(batches): move orphan tests into tests/test\_litellm for CI coverage by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30510](https://github.com/BerriAI/litellm/pull/30510)
- fix(guardrails): run pre\_call hook once for model-level guardrails by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30543](https://github.com/BerriAI/litellm/pull/30543)
- fix(guardrails): stop re-initializing DB guardrails on every poll by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30542](https://github.com/BerriAI/litellm/pull/30542)
- chore(oss): litellm oss staging 150626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30463](https://github.com/BerriAI/litellm/pull/30463)
- ci(lint): add blanket-noqa, dataclass-default, and unused-noqa Ruff rules by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30516](https://github.com/BerriAI/litellm/pull/30516)
- ci: ratchet lint and type-check gates (ruff preview, ANN, mypy, basedpyright) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30379](https://github.com/BerriAI/litellm/pull/30379)
- fix(proxy): allow internal roles to access vector store CRUD routes by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;30503](https://github.com/BerriAI/litellm/pull/30503)
- fix(otel): stamp gen\_ai.input/output.messages on v2 spans by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30548](https://github.com/BerriAI/litellm/pull/30548)
- fix(otel): export v2 gen\_ai client metrics to the configured meter provider by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30549](https://github.com/BerriAI/litellm/pull/30549)
- fix(bedrock): preserve cache\_control for ARN models in /v1/messages adapter by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29823](https://github.com/BerriAI/litellm/pull/29823)
- fix: greatly increase basedpyright slack by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30563](https://github.com/BerriAI/litellm/pull/30563)
- fix(budget): recompute budget\_reset\_at when budget\_duration changes on /budget/update by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30555](https://github.com/BerriAI/litellm/pull/30555)
- fix(otel): accept UPPER\_SNAKE\_CASE OTEL\_INSTRUMENTATION\_GENAI\_CAPTURE\_MESSAGE\_CONTENT in v2 by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30562](https://github.com/BerriAI/litellm/pull/30562)
- chore(lint): remove PLR0915 too-many-statements ruff rule by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30574](https://github.com/BerriAI/litellm/pull/30574)
- ci(lint): ratcheted type-discipline gate (mutable collections, casts, guards, kwargs, suppressions) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30500](https://github.com/BerriAI/litellm/pull/30500)
- feat(proxy): add verification\_uri\_complete to CLI SSO device flow by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30571](https://github.com/BerriAI/litellm/pull/30571)
- chore: litellm oss staging160626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30527](https://github.com/BerriAI/litellm/pull/30527)
- fix(guardrails): return 400 not 500 when AIM blocks a request by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30573](https://github.com/BerriAI/litellm/pull/30573)
- ci(lint): grandfather any-discipline with a per-file ratchet budget (50% headroom) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30582](https://github.com/BerriAI/litellm/pull/30582)
- fix(audio): don't override explicit response\_format with verbose\_json by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30599](https://github.com/BerriAI/litellm/pull/30599)
- fix(anthropic): price and surface response service\_tier in cost tracking by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30558](https://github.com/BerriAI/litellm/pull/30558)
- feat: add dev and wildcard proxy configs for local testing by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30556](https://github.com/BerriAI/litellm/pull/30556)
- fix(proxy): list public team model name in /v1/models by [@&#8203;ishaan-berri](https://github.com/ishaan-berri) in [#&#8203;30588](https://github.com/BerriAI/litellm/pull/30588)
- ci: drop mypy entirely, standardize type checking on basedpyright by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30648](https://github.com/BerriAI/litellm/pull/30648)
- feat(guardrails): surface OpenAI moderation violation\_categories on guardrail traces by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30659](https://github.com/BerriAI/litellm/pull/30659)
- fix(proxy): resolve list files credentials from team BYOK deployments by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;30495](https://github.com/BerriAI/litellm/pull/30495)
- feat(proxy): add --max\_requests\_before\_restart\_jitter to stagger worker restarts by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30601](https://github.com/BerriAI/litellm/pull/30601)
- fix(health): correct bedrock embedding health checks by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30583](https://github.com/BerriAI/litellm/pull/30583)
- test: harden remaining pass-through CI flakes (image-gen spend poll, ruby assistants timeout) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30685](https://github.com/BerriAI/litellm/pull/30685)
- test(pass\_through): harden vertex spendlog poll against transient empty reads by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30683](https://github.com/BerriAI/litellm/pull/30683)
- fix(cost): stop non-string service\_tier from silently dropping cost tracking by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30690](https://github.com/BerriAI/litellm/pull/30690)
- feat(proxy): warn at startup when custom\_auth skips common\_checks enforcement by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;30665](https://github.com/BerriAI/litellm/pull/30665)
- fix(pod\_lock): release cron lock by matching async\_set\_cache JSON encoding by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30600](https://github.com/BerriAI/litellm/pull/30600)
- ci: run a local fake OpenAI endpoint instead of the shared Railway mock by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30695](https://github.com/BerriAI/litellm/pull/30695)
- ci(windows): pin uv to Python 3.11 so it ignores the preinstalled 3.14 by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30704](https://github.com/BerriAI/litellm/pull/30704)
- feat(ui): migrate models page to App Router path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30677](https://github.com/BerriAI/litellm/pull/30677)
- refactor(ui): remove orphaned pass-through-settings route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30692](https://github.com/BerriAI/litellm/pull/30692)
- fix(cost): stop non-string response service\_tier from dropping cost tracking by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30706](https://github.com/BerriAI/litellm/pull/30706)
- feat(agent-shin): automated PR/issue triage, low-quality auto-close, and review-gate label lifecycle by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30433](https://github.com/BerriAI/litellm/pull/30433)
- chore: litellm oss 170626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30637](https://github.com/BerriAI/litellm/pull/30637)
- fix(bedrock\_mantle): add SigV4 fallback to chat completions auth by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30714](https://github.com/BerriAI/litellm/pull/30714)
- feat(search): add TinyFish as search provider by [@&#8203;simantak-dabhade](https://github.com/simantak-dabhade) in [#&#8203;30634](https://github.com/BerriAI/litellm/pull/30634)
- feat(ui): migrate old usage report to App Router path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30694](https://github.com/BerriAI/litellm/pull/30694)
- fix(proxy): enforce budgets against authoritative DB spend when the cross-pod counter is stale by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30684](https://github.com/BerriAI/litellm/pull/30684)
- chore(ci): remove Agent Shin pull\_request\_target workflows by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30784](https://github.com/BerriAI/litellm/pull/30784)
- chore: litellm oss staging by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30745](https://github.com/BerriAI/litellm/pull/30745)
- ci(zizmor): also run on litellm\_internal\_staging by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30789](https://github.com/BerriAI/litellm/pull/30789)
- fix(test): drop references to removed Agent Shin workflows by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30791](https://github.com/BerriAI/litellm/pull/30791)
- chore: remove in-product survey and Claude Code feedback nudges by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30773](https://github.com/BerriAI/litellm/pull/30773)
- feat(ui): migrate api-keys landing to App Router path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30699](https://github.com/BerriAI/litellm/pull/30699)
- feat(proxy): configurable response headers and login-page hint by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;30792](https://github.com/BerriAI/litellm/pull/30792)
- ci(zizmor): gate PRs on medium+ findings and clear existing ones by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30797](https://github.com/BerriAI/litellm/pull/30797)
- fix(proxy): use e.request\_data for logging\_obj in ModifyResponseException streaming passthrough by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30800](https://github.com/BerriAI/litellm/pull/30800)
- chore: make pr template linear portion clearer by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30766](https://github.com/BerriAI/litellm/pull/30766)
- chore(typing): add boto3/botocore stubs so basedpyright resolves the AWS SDK by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30815](https://github.com/BerriAI/litellm/pull/30815)
- fix(otel): one v2 logger owns the global provider; scope tenant OTLP creds per exporter by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;30590](https://github.com/BerriAI/litellm/pull/30590)
- fix(passthrough): recover output tokens for interrupted anthropic streams by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30787](https://github.com/BerriAI/litellm/pull/30787)
- fix(proxy): record partial spend on the failure row for interrupted streams by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30788](https://github.com/BerriAI/litellm/pull/30788)
- fix(ui): repoint dead usage guide link to cost tracking docs by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30859](https://github.com/BerriAI/litellm/pull/30859)
- fix(ui): warn that team models are deleted in the delete-team modal by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29990](https://github.com/BerriAI/litellm/pull/29990)
- feat(caching): add valkey-semantic cache backend and fix semantic cache scope keys by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30675](https://github.com/BerriAI/litellm/pull/30675)
- test(ui): isolate OldTeams delete-warning tests from leaked mock by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30871](https://github.com/BerriAI/litellm/pull/30871)
- feat: add lint-gate target and truncation-proof summary to the strict ruff gate by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30877](https://github.com/BerriAI/litellm/pull/30877)
- chore(ui): rebuild ui for release by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30894](https://github.com/BerriAI/litellm/pull/30894)
- chore(ci): bump deps by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30899](https://github.com/BerriAI/litellm/pull/30899)
- fix(watsonx): wrap string embedding input in array for WatsonX API by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;30897](https://github.com/BerriAI/litellm/pull/30897)
- test: point router/completion/triton tests at the local fake OpenAI endpoint by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30900](https://github.com/BerriAI/litellm/pull/30900)
- feat(sandbox): e2b code execution primitive by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;30898](https://github.com/BerriAI/litellm/pull/30898)
- fix(ui): source api-keys identity from useAuthorized to stop "User ID is not set" by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30903](https://github.com/BerriAI/litellm/pull/30903)
- chore(ui): rebuild ui by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30906](https://github.com/BerriAI/litellm/pull/30906)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30907](https://github.com/BerriAI/litellm/pull/30907)
- fix(redis): prevent forcing SSLConnection when ssl=False in connection pool by [@&#8203;Jacopos311](https://github.com/Jacopos311) in [#&#8203;30770](https://github.com/BerriAI/litellm/pull/30770)
- fix(proxy): log UI setup failures instead of silently swallowing by [@&#8203;sarvesh1327](https://github.com/sarvesh1327) in [#&#8203;30819](https://github.com/BerriAI/litellm/pull/30819)

##### New Contributors

- [@&#8203;simantak-dabhade](https://github.com/simantak-dabhade) made their first contribution in [#&#8203;30634](https://github.com/BerriAI/litellm/pull/30634)
- [@&#8203;Jacopos311](https://github.com/Jacopos311) made their first contribution in [#&#8203;30770](https://github.com/BerriAI/litellm/pull/30770)
- [@&#8203;sarvesh1327](https://github.com/sarvesh1327) made their first contribution in [#&#8203;30819](https://github.com/BerriAI/litellm/pull/30819)

**Full Changelog**: <https://github.com/BerriAI/litellm/compare/v1.89.0...v1.90.0>

### [`v1.90.0`](https://github.com/BerriAI/litellm/releases/tag/v1.90.0)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.89.4...v1.90.0)

##### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.90.0
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.90.0/cosign.pub \
  ghcr.io/berriai/litellm:v1.90.0
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

##### What's Changed

- fix(responses-bridge): map system-only chat request to system input item by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29817](https://github.com/BerriAI/litellm/pull/29817)
- feat(bedrock): forward strict and additionalProperties to Converse toolSpec by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29814](https://github.com/BerriAI/litellm/pull/29814)
- fix(mcp): highlight MCP cards red when the logged-in user is missing per-user env vars by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29856](https://github.com/BerriAI/litellm/pull/29856)
- feat(ui): add budget duration to edit team member form by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29717](https://github.com/BerriAI/litellm/pull/29717)
- fix(ui): make workflow runs page fill full width by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29868](https://github.com/BerriAI/litellm/pull/29868)
- feat: standardize rate limit errors with category, rate\_limit\_type, model, and llm\_provider fields by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;27687](https://github.com/BerriAI/litellm/pull/27687)
- fix(ui): default guardrails page to the Guardrails tab by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29872](https://github.com/BerriAI/litellm/pull/29872)
- docs(readme): add Deploy on AWS/GCP Terraform section and fix deploy button rendering by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29879](https://github.com/BerriAI/litellm/pull/29879)
- refactor(bedrock): build Converse toolSpec via a BedrockToolSpec dict subclass by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29869](https://github.com/BerriAI/litellm/pull/29869)
- feat(litellm): add models and repository layers by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29686](https://github.com/BerriAI/litellm/pull/29686)
- feat(ui): include internal routes in the dashboard's generated OpenAPI types by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29885](https://github.com/BerriAI/litellm/pull/29885)
- feat(proxy): publish /v2/model/info in Swagger OpenAPI spec by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29900](https://github.com/BerriAI/litellm/pull/29900)
- refactor(ui): single source of truth for migrated-page routing by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29949](https://github.com/BerriAI/litellm/pull/29949)
- fix(ui/model-hub): render provider icons on the public model hub by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29958](https://github.com/BerriAI/litellm/pull/29958)
- fix(ui): keep create guardrail modal open on outside click by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29871](https://github.com/BerriAI/litellm/pull/29871)
- fix(ui): label default key type as "Full Access" on key edit page by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29870](https://github.com/BerriAI/litellm/pull/29870)
- fix(ui): unify migrated-route URLs and migrate the API Reference page by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29953](https://github.com/BerriAI/litellm/pull/29953)
- fix(mcp): let non-creator users OAuth into OBO-mode MCP servers from the Tools page by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29867](https://github.com/BerriAI/litellm/pull/29867)
- Litellm oss staging 080626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29932](https://github.com/BerriAI/litellm/pull/29932)
- feat(galileo): add health check support for UI callback test by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29908](https://github.com/BerriAI/litellm/pull/29908)
- fix(model-management): allow deleting a BYOK model after its team is deleted by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29875](https://github.com/BerriAI/litellm/pull/29875)
- feat(jwt-auth): opt-in fallback to DB team on unresolved JWT claim by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28913](https://github.com/BerriAI/litellm/pull/28913)
- fix(team\_endpoints): don't block /team/update on unchanged team budget by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29525](https://github.com/BerriAI/litellm/pull/29525)
- fix(fireworks): enable tool calling for glm-5p1 in model cost map by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29697](https://github.com/BerriAI/litellm/pull/29697)
- fix(vertex): propagate Vertex AI metadata in streaming success callbacks by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29899](https://github.com/BerriAI/litellm/pull/29899)
- fix(ui): show team projects to internal users on key creation by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28855](https://github.com/BerriAI/litellm/pull/28855)
- build(deps): bump pyjwt to 2.13.0 and ws override to 8.20.1 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29982](https://github.com/BerriAI/litellm/pull/29982)
- fix(team-management): delete a team's BYOK models when the team is deleted by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29977](https://github.com/BerriAI/litellm/pull/29977)
- feat(vantage): include organization metadata in FOCUS Tags export by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28184](https://github.com/BerriAI/litellm/pull/28184)
- fix(guardrails): read CrowdStrike AIDR identity from both metadata bags by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29991](https://github.com/BerriAI/litellm/pull/29991)
- fix(mcp): mirror upstream token lifetime instead of forcing a 1h OBO expiry by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29951](https://github.com/BerriAI/litellm/pull/29951)
- feat(azure\_ai): add MAI-Image-2.5 image generation support by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29688](https://github.com/BerriAI/litellm/pull/29688)
- fix(mcp): load MCP tool configuration tools via the OBO/passthrough-aware GET path by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29960](https://github.com/BerriAI/litellm/pull/29960)
- fix(team): reserve team budget raises for proxy admins on /team/update by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;30030](https://github.com/BerriAI/litellm/pull/30030)
- test(ui): data-driven App Router migration E2E smoke (default + server-root-path) by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29974](https://github.com/BerriAI/litellm/pull/29974)
- fix(proxy): extend response headers hook to streaming, TTS, image gen, and pass-through by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;24232](https://github.com/BerriAI/litellm/pull/24232)
- chore(ui): remove dead App Router route stubs under (dashboard) by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30045](https://github.com/BerriAI/litellm/pull/30045)
- fix(ui/mcp): reset OAuth state on create-server modal close so a prior server's token no longer leaks into the next add-server session by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;30000](https://github.com/BerriAI/litellm/pull/30000)
- fix(mcp): allow team access-group grants in OAuth authorize/token access check by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;30041](https://github.com/BerriAI/litellm/pull/30041)
- docs(security): require a reproduction video for vulnerability reports by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30063](https://github.com/BerriAI/litellm/pull/30063)
- feat(ui): add admin flag to disable in-product UI nudges for everyone by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29796](https://github.com/BerriAI/litellm/pull/29796)
- chore(ui): remove dead dashboard files and unused dependencies by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30047](https://github.com/BerriAI/litellm/pull/30047)
- fix(proxy): authorize batch files using upload target\_model\_names (LIT-3593) by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30009](https://github.com/BerriAI/litellm/pull/30009)
- Add Claude Fable 5 across Anthropic, Bedrock, Vertex AI, and Azure AI by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30064](https://github.com/BerriAI/litellm/pull/30064)
- Add Claude Fable 5 cost map entries (data-only hotfix for the hosted map) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30076](https://github.com/BerriAI/litellm/pull/30076)
- fix(caching): restore stored prompt\_tokens on embedding cache hits instead of recomputing by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;30046](https://github.com/BerriAI/litellm/pull/30046)
- Litellm oss 090626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30021](https://github.com/BerriAI/litellm/pull/30021)
- fix(proxy): self-heal startup/reload prisma reads on engine disconnect by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;28803](https://github.com/BerriAI/litellm/pull/28803)
- chore(ui): make knip recognize .mjs scripts and openapi-typescript by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30052](https://github.com/BerriAI/litellm/pull/30052)
- fix(register\_model): preserve built-in cache pricing when registering custom overrides under unmapped keys by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30044](https://github.com/BerriAI/litellm/pull/30044)
- \[internal copy of [#&#8203;28007](https://github.com/BerriAI/litellm/issues/28007)] Fix/gcp model garden streaming by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28363](https://github.com/BerriAI/litellm/pull/28363)
- feat(cli): per-agent `lite claude` / `codex` / `opencode` commands that wrap coding agents through the proxy by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29850](https://github.com/BerriAI/litellm/pull/29850)
- fix(callbacks): forward callback\_settings to callback initializers and guard consumers against non-dict values by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30161](https://github.com/BerriAI/litellm/pull/30161)
- fix(mcp): drop orphaned per-user credential rows when an MCP server is deleted by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;30141](https://github.com/BerriAI/litellm/pull/30141)
- fix(proxy): recover from cached-plan errors by reconnecting the Prisma client by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29983](https://github.com/BerriAI/litellm/pull/29983)
- feat(proxy): add option to disable server-side prepared statements for DB lookups by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29984](https://github.com/BerriAI/litellm/pull/29984)
- fix(release): stop backport releases from overwriting the latest badge by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30005](https://github.com/BerriAI/litellm/pull/30005)
- feat: add conventional commits and coding guidelines by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30159](https://github.com/BerriAI/litellm/pull/30159)
- fix(proxy): return 5xx on DB infra errors during auth; reserve 401 for genuine auth failures by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29986](https://github.com/BerriAI/litellm/pull/29986)
- fix(ui): dev server 404s on migrated-page links because uiBase hardcodes /ui by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30169](https://github.com/BerriAI/litellm/pull/30169)
- refactor(ui): consolidate dashboard to one shell in the (dashboard) layout by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30166](https://github.com/BerriAI/litellm/pull/30166)
- fix(proxy): align /v1/model/info with router deployments by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30025](https://github.com/BerriAI/litellm/pull/30025)
- fix: completion\_cost AttributeError on streaming Anthropic web\_search responses ([#&#8203;26153](https://github.com/BerriAI/litellm/issues/26153)) by [@&#8203;ishaan-berri](https://github.com/ishaan-berri) in [#&#8203;27346](https://github.com/BerriAI/litellm/pull/27346)
- \[internal copy of [#&#8203;30137](https://github.com/BerriAI/litellm/issues/30137)] perf(realtime): eliminate redundant per-frame JSON work on OpenAI realtime relay by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30142](https://github.com/BerriAI/litellm/pull/30142)
- feat(bedrock): aws\_bedrock\_project\_id for bedrock-mantle project / workspace association by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30163](https://github.com/BerriAI/litellm/pull/30163)
- chore(hooks): enforce Conventional Commits and Conventional Branches by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30174](https://github.com/BerriAI/litellm/pull/30174)
- feat(rate-limiter): allow opting out of v3 TPM reservation and Redis circuit breaker by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30211](https://github.com/BerriAI/litellm/pull/30211)
- feat(spend\_logs): opt-in native Postgres partitioning for SpendLogs retention by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29466](https://github.com/BerriAI/litellm/pull/29466)
- feat(ui): migrate playground to path routing and colocate its files by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30185](https://github.com/BerriAI/litellm/pull/30185)
- feat(ui): migrate projects and access-groups to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30226](https://github.com/BerriAI/litellm/pull/30226)
- fix(proxy): coalesce NULL rollup metrics in aggregated daily-activity by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;30151](https://github.com/BerriAI/litellm/pull/30151)
- fix(anthropic\_passthrough): resolve costing model from message\_start chunk, litellm\_params and model\_group instead of 'unknown' by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30160](https://github.com/BerriAI/litellm/pull/30160)
- feat(ui): migrate budgets, workflows, and guardrails-monitor to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30236](https://github.com/BerriAI/litellm/pull/30236)
- feat(ui): migrate mcp-servers, search-tools, tag-management, vector-stores, and memory to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30261](https://github.com/BerriAI/litellm/pull/30261)
- fix(a2a): forward agent\_extra\_headers through completion bridge by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28277](https://github.com/BerriAI/litellm/pull/28277)
- fix(gemini-live): forward audio buffer commit and correct Vertex PCM rate by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29946](https://github.com/BerriAI/litellm/pull/29946)
- fix(proxy): skip double-wrapping unified batch output file ids on retrieve by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30011](https://github.com/BerriAI/litellm/pull/30011)
- feat: litellm oss 110626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30202](https://github.com/BerriAI/litellm/pull/30202)
- fix(docker): copy only runtime artifacts into the final image by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30243](https://github.com/BerriAI/litellm/pull/30243)
- feat(proxy): enforce key/team guardrails on bedrock passthrough routes by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30194](https://github.com/BerriAI/litellm/pull/30194)
- feat(gemini): forward web search tools in image generation by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30119](https://github.com/BerriAI/litellm/pull/30119)
- fix: bedrock mantle fixes by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30083](https://github.com/BerriAI/litellm/pull/30083)
- feat(proxy): add require\_managed\_files setting for file uploads by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30186](https://github.com/BerriAI/litellm/pull/30186)
- fix(mcp): honor server\_id for REST tool calls with shared upstream URLs by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30184](https://github.com/BerriAI/litellm/pull/30184)
- fix(responses): presidio PII masking for Azure WebSocket and streaming by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30003](https://github.com/BerriAI/litellm/pull/30003)
- feat(passthrough): add configurable pass-through request timeouts by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30266](https://github.com/BerriAI/litellm/pull/30266)
- fix(google\_genai): preserve complete SSE events in Vertex/Gemini image streaming by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30270](https://github.com/BerriAI/litellm/pull/30270)
- fix(proxy): populate access\_via\_team\_ids on /v1/model/info by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30274](https://github.com/BerriAI/litellm/pull/30274)
- chore(oss): litellm oss staging 120626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30292](https://github.com/BerriAI/litellm/pull/30292)
- feat(ui): migrate policies, guardrails, prompts, tool-policies, and skills to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30263](https://github.com/BerriAI/litellm/pull/30263)
- feat(ui): migrate caching, cost-tracking, transform-request, ui-theme, and logs to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30267](https://github.com/BerriAI/litellm/pull/30267)
- fix(ui): gate dashboard layout on ui config load so deep links work under SERVER\_ROOT\_PATH by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30312](https://github.com/BerriAI/litellm/pull/30312)
- feat(ui): migrate admin-panel, logging-and-alerts, model-hub-table, and usage to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30268](https://github.com/BerriAI/litellm/pull/30268)
- fix(otel): cap metric attribute cardinality with include/exclude lists by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30257](https://github.com/BerriAI/litellm/pull/30257)
- fix(proxy): grace-period key rotation 401s; return deprecated-key lookup result directly by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30327](https://github.com/BerriAI/litellm/pull/30327)
- chore(deps): bump vitest, brace-expansion, pypdf and tornado by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30220](https://github.com/BerriAI/litellm/pull/30220)
- refactor(ui): remove unreachable /chat page by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30178](https://github.com/BerriAI/litellm/pull/30178)
- feat(ui): migrate agents and router-settings to path routes by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30323](https://github.com/BerriAI/litellm/pull/30323)
- feat: strengthen coding conventions in CLAUDE.md by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30333](https://github.com/BerriAI/litellm/pull/30333)
- feat(ui): cut the users page over to the /ui/users path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30334](https://github.com/BerriAI/litellm/pull/30334)
- feat: ruff strict-rule suppressions baseline gate by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30303](https://github.com/BerriAI/litellm/pull/30303)
- feat(guardrails): add Cisco AI Defense integration ([#&#8203;28249](https://github.com/BerriAI/litellm/issues/28249)) by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30338](https://github.com/BerriAI/litellm/pull/30338)
- chore(ui): remove dead UI components unreferenced by any page by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30340](https://github.com/BerriAI/litellm/pull/30340)
- ci: add osv-scanner lockfile scan workflow by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30222](https://github.com/BerriAI/litellm/pull/30222)
- fix(otel): record full error message on standard exception event in otel v2 by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30380](https://github.com/BerriAI/litellm/pull/30380)
- test(fireworks): mock whisper transcription tests instead of live calls by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30391](https://github.com/BerriAI/litellm/pull/30391)
- build(ui): pin esbuild to 0.28.1 via overrides by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30390](https://github.com/BerriAI/litellm/pull/30390)
- feat(ui): cut the organizations page over to the /ui/organizations path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30336](https://github.com/BerriAI/litellm/pull/30336)
- fix(proxy): support SMTP implicit SSL (port 465) by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30395](https://github.com/BerriAI/litellm/pull/30395)
- fix(mcp): default Linear MCP registry entry to streamable HTTP by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30396](https://github.com/BerriAI/litellm/pull/30396)
- fix(ui): stop Virtual Keys page from infinite render loop by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30397](https://github.com/BerriAI/litellm/pull/30397)
- fix(streaming): guard raise\_on\_model\_repetition against empty choices by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;30485](https://github.com/BerriAI/litellm/pull/30485)
- feat(otel-v2): emit the 6 gen\_ai.client.\* metrics at parity with v1 by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30326](https://github.com/BerriAI/litellm/pull/30326)
- fix(mcp): drop phantom 401 span on delegated OAuth2 tool calls by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30494](https://github.com/BerriAI/litellm/pull/30494)
- feat(ui): cut the teams page over to the /ui/teams path route by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;30343](https://github.com/BerriAI/litellm/pull/30343)
- fix(integrations): cap Anthropic cache\_control injection at 4 blocks by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;30480](https://github.com/BerriAI/litellm/pull/30480)
- chore(codecov): add Batches, Videos, and Realtime components by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30517](https://github.com/BerriAI/litellm/pull/30517)
- test(batches): move orphan tests into tests/test\_litellm for CI coverage by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;30510](https://github.com/BerriAI/litellm/pull/30510)
- fix(guardrails): run pre\_call hook once for model-level guardrails by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;30543](http…
mookim-eth pushed a commit to mookim-eth/litellm that referenced this pull request Jul 17, 2026
…ross-pod counter is unreliable (BerriAI#30684)

Budget enforcement reads spend from the cross-pod Redis counter via get_current_spend, which trusted the counter whenever Redis returned a value. A Redis instance that restarts and reloads an older RDB snapshot (the customer's logs repeat "Redis is loading the dataset in memory") comes back with a stale-low counter; that read is a hit, not a clean miss, so the existing DB reseed never ran and a key kept getting admitted even though its recorded spend was already over max_budget. The symptom was recorded spend sitting above the limit while requests kept succeeding.

Read-time enforcement: get_current_spend takes an optional max_budget and, when the counter would admit the request but reads below this caller's last-known recorded spend, re-reads the authoritative spend and enforces against the higher value. The authoritative source depends on the counter: key/team/user/org/team-member read the DB row, per-window budgets aggregate spend logs, and end-user/tag have no DB row so the caller's freshly-loaded recorded spend is used. Healthy primary counters and freshly reset keys stay off the DB path, and the value is cached in-process for a few seconds, so a persistently stale counter drives at most one read per counter per window. When the DB value is higher, the counter is repaired with a monotonic, atomic set-max (RedisCache.async_set_max) so every worker reads the corrected total and a concurrent increment is never clobbered.

Reconcile no longer fails open: when the post-call reservation reconcile found the counter missing or an adjustment that would drive it negative, it deleted the counter and continued (the deletion is what left counters nil/unenforced after a Redis reload). It now reseeds from the DB's lagging authoritative floor instead of deleting; the monotonic set-max can only raise a stale-low counter, and the read-time floor converges to the true total as the spend buffer flushes. The pre-call admission resize path keeps its original fail-closed behavior.

Opt-in strict enforcement: general_settings.fail_closed_budget_enforcement (default False) makes the authoritative re-check run for every budgeted entity (closing the gap where a stale-low counter and a stale-low cached fallback would otherwise both pass the cheap guard), and rejects a request with 503 when the spend backing an admit decision can be verified against neither Redis nor the database. Default behavior is unchanged; the re-check stays bounded by the in-process cache.

Resolves LIT-3772
JosXa added a commit to JosXa/litellm that referenced this pull request Jul 30, 2026
…ent doubled team spend

A recurring production incident intermittently rejected every request for an affected team even though the persisted LiteLLM_TeamTable.spend remained below its budget. The budget error reported "Budget has been exceeded! Team=coding Current cost: 31786.6605882599, Max budget: 20000.0", where Current cost was exactly 2x the persisted spend of 15893.33029412995. Every team member was falsely rejected for roughly ten minutes until the counter TTL expired, after which the incident could recur unpredictably.

The no-Redis failure was an unlocked writer race in one Python process. First, a cold counter entered SpendCounterReseed.coalesced(), acquired its per-counter asyncio lock, read DB spend B, and yielded while awaiting the database. Second, _repair_stale_spend_counter(), or reseed_spend_counter_from_db() through budget reservation reconciliation, read the same DB spend and wrote B directly to the counter without acquiring that lock. Third, the cold reseed resumed and used additive async_increment_cache(key, B), leaving the enforcement counter at 2B.

The original singleflight lock did not prevent this because repair writers bypassed it. This race does not need Redis or multiple workers: it occurs in a single worker with only the in-memory cache. The Redis branch already uses atomic SET NX from upstream PR BerriAI#27854, but the no-Redis additive branch and the unlocked repair route introduced by PR BerriAI#30684 remained vulnerable. This is the same spend-counter class of problem tracked in upstream issue BerriAI#27735.

If left unfixed, the active budgeted value can be doubled. The enforcement counter reaches 2x real spend, or Nx real spend when repeated interleavings stack the DB value, causing every request for the team to be falsely rejected with "Budget has been exceeded" despite real headroom. TTL expiry temporarily heals the counter, making the outage recurrent and difficult to predict.

Serialize stale-counter repairs on SpendCounterReseed's per-counter lock, which also covers reservation reconciliation through reseed_spend_counter_from_db(). In the no-Redis cold-seed branch, re-check the in-memory counter after the DB await and use the value already written by another lifecycle writer instead of additively applying DB spend. Serialize no-Redis request-cost increments on that same lock so a legitimate cost waits for the seed and is applied as B + cost rather than being lost or mistaken for an initialized counter.

Add asynchronous regressions for a cold reseed racing a direct repair, a reservation-reconciliation reseed plus a request increment, and a request increment during the cold reseed. The tests prove the final counter remains B or B + cost rather than doubling.
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.

3 participants