Skip to content

[Bug]: /customer/update silently drops budget_duration when creating new budget for end-user without existing budget #33941

Description

@impr3ssi0n

Check for existing issues

  • I have searched the existing issues and checked that my issue is not a duplicate.

What happened?

When calling POST /customer/update on an end-user that has no existing budget (budget_id=null, e.g. after the budget was deleted via /budget/delete which triggers FK ON DELETE SET NULL), and the request includes both max_budget and budget_duration, a new budget row is created in LiteLLM_BudgetTable, but budget_duration and budget_reset_at are silently set to null. Only max_budget is persisted on the new budget row.

The end-user appears to have a budget (budget_id is set, litellm_budget_table.max_budget is set), but the reset period is missing — so reset_budget_job never resets spend for this budget, and the customer effectively has a one-time non-resetting cap.

What did you expect to happen?

When /customer/update creates a new budget (because end_user_budget_table is None), it should persist budget_duration and compute budget_reset_at — exactly like /budget/new does (budget_management_endpoints.py:86-87):

if budget_obj.budget_reset_at is None and budget_obj.budget_duration is not None:
    budget_obj.budget_reset_at = get_budget_reset_time(budget_duration=budget_obj.budget_duration)

Currently customer_endpoints.py (lines ~596-608) calls BudgetRepository(prisma_client).table.create(data=budget_table_data) directly without computing budget_reset_at from budget_duration, so both fields end up as null in the DB.

Steps to Reproduce

Setup: end-user must exist but have budget_id=null. Either create a fresh end-user with no budget, or delete its budget first.

# 1. Create end-user (skip if already exists)
curl -X POST "https://your-proxy.example.com/customer/new" \
  -H "Authorization: Bearer sk-xxxx" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "test-bug-repro@example.com"}'

# 2. Update end-user with max_budget + budget_duration (user has no existing budget)
curl -X POST "https://your-proxy.example.com/customer/update" \
  -H "Authorization: Bearer sk-xxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "test-bug-repro@example.com",
    "max_budget": 50.0,
    "budget_duration": "1mo"
  }'

# 3. Fetch customer info — inspect litellm_budget_table
curl "https://your-proxy.example.com/customer/info?end_user_id=test-bug-repro@example.com" \
  -H "Authorization: Bearer sk-xxxx"

Actual result (step 3):

{
  "user_id": "test-bug-repro@example.com",
  "litellm_budget_table": {
    "budget_id": "69ec4697-4577-400e-9192-edeba7685837",
    "max_budget": 50.0,
    "budget_duration": null,
    "budget_reset_at": null
  }
}

Expected result:

{
  "user_id": "test-bug-repro@example.com",
  "litellm_budget_table": {
    "budget_id": "...",
    "max_budget": 50.0,
    "budget_duration": "1mo",
    "budget_reset_at": "2026-08-01T00:00:00.000Z"
  }
}

Relevant log output

What part of LiteLLM is this about?

Proxy

What LiteLLM version are you on ?

v1.90

Twitter / LinkedIn details

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingproxy

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions