Skip to content

fix(organization): clear org budget limits when PATCH /organization/update sends null - #39670

Merged
ryan-crabbe-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_fix_org_update_null_budget_limits
Sep 4, 2026
Merged

ryan-crabbe-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_fix_org_update_null_budget_limits

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • PATCH /organization/update with "tpm_limit": null returned 200 but changed nothing
  • Same for rpm_limit, max_budget and the other budget fields, top level or nested under litellm_budget_table
  • Only workaround was calling POST /budget/update directly

How it solves it:

  • Budget fields are collected by presence (model_fields_set), not by v is not None
  • A sent null now reaches /budget/update, which already writes nulls through
  • Same merge-patch semantics PATCH /v2/organization/{organization_id} already has

User Flow

Before: an admin trying to remove an org's TPM limit over the API gets a 200 but the limit stays

  1. They send POST https://litellm-domain/organization/new with {"organization_alias": "my-org", "tpm_limit": 2026, "rpm_limit": 99} and get back an organization_id and a budget showing tpm_limit: 2026
  2. They send PATCH https://litellm-domain/organization/update with {"organization_id": "<id>", "tpm_limit": null}
  3. The response is HTTP 200 but litellm_budget_table.tpm_limit is still 2026
  4. GET https://litellm-domain/organization/info?organization_id= and the Organization Settings page still show TPM 2026

After: the same request clears the limit

  1. They send POST https://litellm-domain/organization/new with {"organization_alias": "my-org", "tpm_limit": 2026, "rpm_limit": 99} and get back an organization_id and a budget showing tpm_limit: 2026
  2. They send PATCH https://litellm-domain/organization/update with {"organization_id": "<id>", "tpm_limit": null}
  3. The response is HTTP 200 and litellm_budget_table.tpm_limit is null; rpm_limit is still 99 because it was not sent
  4. GET https://litellm-domain/organization/info?organization_id= shows tpm_limit: null

Relevant issues

Linear ticket

Resolves LIT-5769

Pre-Submission checklist

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

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

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Shared setup: proxy running litellm/proxy/dev_config.yaml on localhost against local Postgres, master key sk-1234

ORG=$(curl -s -X POST http://localhost:4000/organization/new \
  -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' \
  -d '{"organization_alias":"lit5769-repro","max_budget":12.5,"tpm_limit":2026,"rpm_limit":99}')
ORG_ID=$(echo "$ORG" | jq -r .organization_id)
BUDGET_ID=$(echo "$ORG" | jq -r .budget_id)
echo "$ORG" | jq -c '{organization_id, budget_id, tpm_limit: .litellm_budget_table.tpm_limit, rpm_limit: .litellm_budget_table.rpm_limit}'
{"organization_id":"...","budget_id":"...","tpm_limit":2026,"rpm_limit":99}

Before (a5b3bc8)

Top-level tpm_limit: null

  1. curl -s -w '\nHTTP %{http_code}\n' -X PATCH http://localhost:4000/organization/update -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d "{\"organization_id\":\"$ORG_ID\",\"tpm_limit\":null}" | jq -c '.litellm_budget_table | {tpm_limit, rpm_limit, max_budget}'
  2. {"tpm_limit":2026,"rpm_limit":99,"max_budget":12.5} then HTTP 200
  3. curl -s "http://localhost:4000/organization/info?organization_id=$ORG_ID" -H 'Authorization: Bearer sk-1234' | jq -c '.litellm_budget_table | {tpm_limit, rpm_limit, max_budget}'
  4. {"tpm_limit":2026,"rpm_limit":99,"max_budget":12.5}

Nested litellm_budget_table.rpm_limit: null

  1. curl -s -X PATCH http://localhost:4000/organization/update -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d "{\"organization_id\":\"$ORG_ID\",\"litellm_budget_table\":{\"rpm_limit\":null}}" | jq -c '.litellm_budget_table | {tpm_limit, rpm_limit, max_budget}'
  2. {"tpm_limit":2026,"rpm_limit":99,"max_budget":12.5}

After (04a2407, tip fb4cbcc only merges the base branch on top with no code changes)

Top-level tpm_limit: null

  1. curl -s -w '\nHTTP %{http_code}\n' -X PATCH http://localhost:4000/organization/update -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d "{\"organization_id\":\"$ORG_ID\",\"tpm_limit\":null}" | jq -c '.litellm_budget_table | {tpm_limit, rpm_limit, max_budget}'
  2. {"tpm_limit":null,"rpm_limit":99,"max_budget":12.5} then HTTP 200
  3. curl -s "http://localhost:4000/organization/info?organization_id=$ORG_ID" -H 'Authorization: Bearer sk-1234' | jq -c '.litellm_budget_table | {tpm_limit, rpm_limit, max_budget}'
  4. {"tpm_limit":null,"rpm_limit":99,"max_budget":12.5}

Nested litellm_budget_table.rpm_limit: null

  1. curl -s -X PATCH http://localhost:4000/organization/update -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d "{\"organization_id\":\"$ORG_ID\",\"litellm_budget_table\":{\"rpm_limit\":null}}" | jq -c '.litellm_budget_table | {tpm_limit, rpm_limit, max_budget}'
  2. {"tpm_limit":null,"rpm_limit":null,"max_budget":12.5}

Omitted budget fields are left alone (rename only)

  1. curl -s -X PATCH http://localhost:4000/organization/update -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d "{\"organization_id\":\"$ORG_ID\",\"organization_alias\":\"lit5769-renamed\"}" | jq -c '{organization_alias, budget: (.litellm_budget_table | {tpm_limit, rpm_limit, max_budget})}'
  2. {"organization_alias":"lit5769-renamed","budget":{"tpm_limit":null,"rpm_limit":null,"max_budget":12.5}}
  3. psql -d litellm -tAc "select tpm_limit, rpm_limit, max_budget from \"LiteLLM_BudgetTable\" where budget_id='$BUDGET_ID'" prints ||12.5

Type

🐛 Bug Fix

Caveats (if any)

Low

  • Nested litellm_budget_table payloads now also pass nulls through, so a client echoing back a full budget object with null fields will clear them. The Admin UI no longer uses this endpoint (it moved to /v2/organization/{organization_id}), so no UI flow is affected

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Link to Devin session: https://app.devin.ai/sessions/3bf227d1a7b4452e9291e303a8a3d9c3
Open in Devin Desktop: https://app.devin.ai/desktop/session/3bf227d1a7b4452e9291e303a8a3d9c3?variant=devin

…pdate sends null

A sent null for tpm_limit, rpm_limit, max_budget and the other budget fields
was dropped by a 'v is not None' filter, so update_budget was never called and
the request returned 200 without changing the budget row. Presence is now read
from model_fields_set (merge-patch semantics, matching /v2/organization) and
the nested litellm_budget_table payload no longer drops nulls either

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

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@codspeed

codspeed Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_fix_org_update_null_budget_limits (4f3b023) with litellm_internal_staging (04a198e)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (f74bc94) during the generation of this report, so 04a198e was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@greptile-apps

greptile-apps Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR gives the legacy organization-update endpoint merge-patch semantics for budget fields.

  • Explicit null values now clear top-level and nested budget limits.
  • Omitted budget fields remain unchanged.
  • Regression tests cover null clearing and non-budget updates.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
litellm/proxy/management_endpoints/organization_endpoints.py Uses field presence rather than non-null values to propagate explicit budget clears while preserving omitted fields.
tests/test_litellm/proxy/management_endpoints/test_organization_endpoints.py Adds typed test coverage for top-level and nested null clears and verifies omitted budget fields do not trigger writes.

Reviews (2): Last reviewed commit: "test(organization): type the legacy upda..." | Re-trigger Greptile

@codecov

codecov Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor

@greptile re review

@ryan-crabbe-berri
ryan-crabbe-berri merged commit a502c72 into litellm_internal_staging Sep 4, 2026
181 of 184 checks passed
@ryan-crabbe-berri
ryan-crabbe-berri deleted the litellm_fix_org_update_null_budget_limits branch September 4, 2026 20:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants