fix: set budget_reset_at when creating customer with budget_duration - #22009
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR fixes a bug where creating a customer via
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| litellm/proxy/management_endpoints/customer_endpoints.py | Correctly adds budget_reset_at initialization to new_budget_request(), mirroring the existing pattern in /budget/new. However, the same bug also exists in the update_end_user path (line 591-605) where a new budget is created without setting budget_reset_at. |
| tests/test_litellm/proxy/management_endpoints/test_customer_budget.py | Adds a clean unit test that verifies budget_reset_at is auto-populated when budget_duration is provided. No network calls, proper assertions with time-range bounds. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["POST /customer/new\nwith budget_duration"] --> B["new_budget_request()"]
B --> C{"budget_duration set\nbudget_reset_at null?"}
C -- "Yes (NEW FIX)" --> D["Set budget_reset_at =\nnow + duration_in_seconds()"]
C -- "No" --> E["Keep existing budget_reset_at"]
D --> F["Create budget row in DB\nwith budget_reset_at populated"]
E --> F
F --> G["Create end-user row\nlinked to budget"]
G --> H["ResetBudgetJob cron"]
H --> I{"budget_reset_at\nin the future?"}
I -- "Yes" --> J["Skip — budget not yet due"]
I -- "No / NULL (OLD BUG)" --> K["Reset spend to 0.0\nprematurely"]
Last reviewed commit: 26f29c5
Additional Comments (1)
The Consider applying the same if budget_table_data:
# Set budget_reset_at if budget_duration is present but budget_reset_at is not
if "budget_reset_at" not in budget_table_data and "budget_duration" in budget_table_data:
budget_table_data["budget_reset_at"] = datetime.utcnow() + timedelta(
seconds=duration_in_seconds(duration=budget_table_data["budget_duration"])
)
if end_user_budget_table is None: |
Review1. Does this PR fix the issue it describes? 2. Has this issue already been solved elsewhere? 3. Are there other PRs addressing the same problem? 4. Are there other issues this potentially closes? ✅ LGTM — clean, focused fix with test coverage. Well-documented root cause analysis. |
9bf49d8
into
BerriAI:litellm_oss_staging_03_04_2026
Relevant issues
Fixes #22013
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewCI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Type
🐛 Bug Fix
Changes
Problem
When an end-user is created via
/customer/newwithbudget_durationbut without an explicitbudget_reset_at, the budget row is created withbudget_reset_at = NULL. The periodicResetBudgetJobcron has a query branch that matches budgets wherebudget_reset_at IS NULL AND budget_duration IS NOT NULL, which picks up these freshly created budgets and unconditionally resets the linked end-user's spend to0.0via an upsert — wiping out legitimate spend within minutes of it being recorded.Meanwhile,
LiteLLM_DailyEndUserSpend(which the reset job doesn't touch) retains the correct values, creating a discrepancy between the two tables.Steps to reproduce
budget_durationbut nobudget_reset_at:Run a chat completion against that user to accumulate spend.
Wait for the
ResetBudgetJobcron to fire (~10 minutes by default).Observe
LiteLLM_EndUserTable.spendis reset to0.0, even though the 30-day duration has not elapsed.LiteLLM_DailyEndUserSpendretains the correct value.Workaround: Explicitly passing
budget_reset_atin the request avoids the issue.Root cause
The
new_budget_request()helper incustomer_endpoints.pyconstructs aBudgetNewRequestfrom the incomingNewCustomerRequestbut never initializesbudget_reset_atwhenbudget_durationis provided. The/budget/newendpoint already handles this correctly — the same logic was simply missing from the/customer/newpath.Fix
Added the same
budget_reset_atinitialization that/budget/newuses tonew_budget_request():This ensures the budget row is created with a proper future
budget_reset_at, so the reset cron only picks it up after the duration has actually elapsed.Test
test_new_budget_request_sets_budget_reset_at_when_duration_providedintests/test_litellm/proxy/management_endpoints/test_customer_budget.py— verifiesbudget_reset_atis auto-populated ~30 days in the future whenbudget_duration="30d"is provided without an explicitbudget_reset_atNote
Re: Greptile's comment on the update path
The update path is not affected by this bug.
This bug only occurs during budget creation (not linking to an existing budget via
budget_id).UpdateCustomerRequest(_types.py:L1439) does not acceptbudget_durationas a field — Pydantic silently drops it. So even when/customer/updatecreates a new budget (line 595),budget_durationwill always beNULL, which means the reset cron's query (budget_duration IS NOT NULL) will never match it.