Skip to content

fix: make litellm_budget_table optional to prevent Field required 401 crash - #28700

Merged
Sameerlite merged 3 commits into
BerriAI:litellm_010626from
Touseeq99:fix/team-membership-budget-table-default
Jun 1, 2026
Merged

fix: make litellm_budget_table optional to prevent Field required 401 crash#28700
Sameerlite merged 3 commits into
BerriAI:litellm_010626from
Touseeq99:fix/team-membership-budget-table-default

Conversation

@Touseeq99

@Touseeq99 Touseeq99 commented May 23, 2026

Copy link
Copy Markdown

Relevant issues

Fixes #28689

Type

🐛 Bug Fix

Changes

Pydantic v2 treats Optional[T] without a default as required. When budget_id
is null, the DB join returns no litellm_budget_table key, causing model_validate
to raise Field required → 401 auth crash. Adding = None matches the behavior
of budget_id and spend fields.

Pre-Submission checklist

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • 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
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Delays in PR merge?

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

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.

Screenshots / Proof of Fix

Before fix:
pydantic_core._pydantic_core.ValidationError: Field required [type=missing] → 401 auth crash when budget_id is null

After fix:
image

Touseeq99 added 2 commits May 23, 2026 19:37
…_table

Fixes BerriAI#28689

Pydantic v2 treats Optional[T] without a default as required.
When budget_id is null, the DB join returns no litellm_budget_table key,
causing model_validate to raise 'Field required' → 401 auth crash.
Adding = None matches the behavior of budget_id and spend fields.
@Touseeq99

Copy link
Copy Markdown
Author

@greptileai

@greptile-apps

greptile-apps Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a Pydantic v2 "Field required" crash in LiteLLM_TeamMembership by adding = None to litellm_budget_table, which was declared Optional but lacked a default value — causing model_validate to raise a 401 error whenever the DB join returned no budget row.

  • litellm/proxy/_types.py: One-line fix giving litellm_budget_table an explicit = None default, aligning it with the already-defaulted budget_id and spend fields.
  • tests/test_litellm/test__types.py: Two new unit tests added; the first correctly exercises the absent-key regression path; the second's docstring claims to verify a populated budget table but passes None (noted in prior review threads).

Confidence Score: 5/5

The change is a targeted, backwards-compatible default-value addition that restores expected behaviour without altering any logic.

The fix is a single-field default addition that exactly matches the root cause described in the issue. No logic paths are altered, no existing defaults are changed, and the first regression test directly validates the previously-crashing scenario.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/_types.py Adds = None default to litellm_budget_table in LiteLLM_TeamMembership, fixing the Pydantic v2 "Field required" crash when the DB join returns no budget row.
tests/test_litellm/test__types.py New regression tests for LiteLLM_TeamMembership; first test correctly covers the missing-key path; second test's docstring claims to verify a populated budget table but passes None instead.

Reviews (3): Last reviewed commit: "fix: linting" | Re-trigger Greptile

Comment thread tests/test_litellm/test__types.py Outdated
Comment on lines +23 to +32
def test_team_membership_budget_table_present_still_works():
"""When budget_id exists, litellm_budget_table should still be populated."""
data = {
"user_id": "test-user",
"team_id": "test-team",
"budget_id": "some-budget-id",
"litellm_budget_table": None,
}
result = LiteLLM_TeamMembership.model_validate(data)
assert result.litellm_budget_table is None No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The second test's docstring says "When budget_id exists, litellm_budget_table should still be populated," but the test body passes litellm_budget_table: None and asserts None — it never actually validates that a real budget-table object is accepted and round-trips through model_validate. The test description is misleading and the scenario it claims to cover (budget table present) is untested.

Suggested change
def test_team_membership_budget_table_present_still_works():
"""When budget_id exists, litellm_budget_table should still be populated."""
data = {
"user_id": "test-user",
"team_id": "test-team",
"budget_id": "some-budget-id",
"litellm_budget_table": None,
}
result = LiteLLM_TeamMembership.model_validate(data)
assert result.litellm_budget_table is None
def test_team_membership_budget_table_present_still_works():
"""When budget_id exists and litellm_budget_table data is present, it should be populated."""
data = {
"user_id": "test-user",
"team_id": "test-team",
"budget_id": "some-budget-id",
"litellm_budget_table": {
"budget_id": "some-budget-id",
"max_budget": 100.0,
"rpm_limit": 10,
"tpm_limit": 1000,
},
}
result = LiteLLM_TeamMembership.model_validate(data)
assert result.litellm_budget_table is not None
assert result.litellm_budget_table.max_budget == 100.0

@greptile-apps

greptile-apps Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a Pydantic v2 Field required crash in LiteLLM_TeamMembership by adding = None as the default for litellm_budget_table. Without the default, Pydantic v2 treats Optional[T] as a required field and raises a validation error when the DB join returns no budget row (i.e., when budget_id is null), causing a 401 on every such request.

  • litellm/proxy/_types.py: One-line fix — adds = None default to litellm_budget_table, consistent with the existing budget_id and spend fields in the same class.
  • tests/test_litellm/test__types.py: New regression tests covering the absent-key path; the second test's name implies a populated budget table but still passes None, leaving the actual deserialization of a budget-table object untested.

Confidence Score: 5/5

The one-line change is correct and well-targeted; adding the = None default restores the expected permissive behavior for a nullable FK field without touching any other logic.

The fix is minimal and precise: it aligns litellm_budget_table with the other nullable fields in the same class. The test file provides regression coverage for the crash scenario. The only gaps are a misleading second test case and cosmetic trailing whitespace, neither of which affect correctness.

The second test in tests/test_litellm/test__types.py should be strengthened to actually pass a populated budget-table dict, so that the deserialization path for a present budget row is also covered.

Important Files Changed

Filename Overview
litellm/proxy/_types.py Adds = None default to litellm_budget_table in LiteLLM_TeamMembership, fixing a Pydantic v2 Field required crash when the DB join returns no budget row; also leaves two trailing blank lines after the field.
tests/test_litellm/test__types.py New regression tests for the fix; test_team_membership_budget_table_present_still_works uses a misleading name — it passes litellm_budget_table: None rather than an actual populated budget table object, so it does not cover the "table is present" path it advertises.

Reviews (2): Last reviewed commit: "test(proxy): add regression test for Lit..." | Re-trigger Greptile

Comment thread tests/test_litellm/test__types.py Outdated
Comment on lines +23 to +32
def test_team_membership_budget_table_present_still_works():
"""When budget_id exists, litellm_budget_table should still be populated."""
data = {
"user_id": "test-user",
"team_id": "test-team",
"budget_id": "some-budget-id",
"litellm_budget_table": None,
}
result = LiteLLM_TeamMembership.model_validate(data)
assert result.litellm_budget_table is None No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The test name says "present still works", but litellm_budget_table is set to None — so it only exercises the same absent-table path as the first test. An actual populated LiteLLM_BudgetTable or LiteLLM_BudgetTableFull object should be passed to verify that the field is accepted and deserialized correctly when a budget row IS returned from the DB join.

Suggested change
def test_team_membership_budget_table_present_still_works():
"""When budget_id exists, litellm_budget_table should still be populated."""
data = {
"user_id": "test-user",
"team_id": "test-team",
"budget_id": "some-budget-id",
"litellm_budget_table": None,
}
result = LiteLLM_TeamMembership.model_validate(data)
assert result.litellm_budget_table is None
def test_team_membership_budget_table_present_still_works():
"""When budget_id exists and a budget row is returned, litellm_budget_table should be populated."""
from litellm.proxy._types import LiteLLM_BudgetTable
data = {
"user_id": "test-user",
"team_id": "test-team",
"budget_id": "some-budget-id",
"litellm_budget_table": {"budget_id": "some-budget-id", "max_budget": 10.0},
}
result = LiteLLM_TeamMembership.model_validate(data)
assert result.litellm_budget_table is not None
assert result.litellm_budget_table.max_budget == 10.0

Comment thread litellm/proxy/_types.py Outdated
Comment on lines 3889 to 3893
litellm_budget_table: Optional[Union[LiteLLM_BudgetTableFull, LiteLLM_BudgetTable]] = None



def safe_get_team_member_rpm_limit(self) -> Optional[int]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Two trailing blank lines were left after the field definition. Only one blank line is needed to separate it from the method definitions.

Suggested change
litellm_budget_table: Optional[Union[LiteLLM_BudgetTableFull, LiteLLM_BudgetTable]] = None
def safe_get_team_member_rpm_limit(self) -> Optional[int]:
litellm_budget_table: Optional[Union[LiteLLM_BudgetTableFull, LiteLLM_BudgetTable]] = None
def safe_get_team_member_rpm_limit(self) -> Optional[int]:

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@codecov

codecov Bot commented May 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Touseeq99

Touseeq99 commented May 23, 2026

Copy link
Copy Markdown
Author

@ishaan-jaff could you please review this when you get a chance?
This fixes the Field required 401 crash from issue #28689.

@Touseeq99

Copy link
Copy Markdown
Author

@Sameerlite, @krrish-berri-2, would you please help review?

@Touseeq99

Copy link
Copy Markdown
Author

@greptileai review

Touseeq99

This comment was marked as outdated.

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

@Sameerlite
Sameerlite changed the base branch from litellm_internal_staging to litellm_010626 June 1, 2026 11:15
@Sameerlite
Sameerlite merged commit 693312d into BerriAI:litellm_010626 Jun 1, 2026
43 of 44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants