Repository navigation
feat(team): show whether a member follows the team default budget and allow resetting to it - #41906
Merged
Merged
Conversation
… allow resetting to it
Adds budget_source (team_default, custom, none) to each membership in /team/info and a
POST /team/{team_id}/member/{user_id}/reset_budget route that relinks a member to the team's
shared team_member_budget row without touching their spend. The Admin UI team members table
shows a Team default or Custom badge next to each member's budget and offers a
Use team default action on customized members
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…udget_source_reset
devin-ai-integration
Bot
requested a review
from yuneng-berri
as a code owner
September 19, 2026 00:38
Contributor
Author
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
devin-ai-integration
Bot
requested a review
from ryan-crabbe-berri
as a code owner
September 19, 2026 00:38
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Contributor
|
Derive budget_source from the budget row /team/info actually loaded, so a metadata id whose row was removed via /budget/delete reads as none instead of team_default. Share the /team/info test scaffolding so the added patch calls stay within the TQ008 budget, and allowlist the imperative reset_budget route in the provider endpoint audit Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Contributor
Author
…ver it in the behavior suite Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…assertions Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Contributor
Author
Contributor
Author
Contributor
|
bugbot run |
Contributor
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 8bd9d35. Configure here.
yuneng-berri
approved these changes
Sep 19, 2026
ryan-crabbe-berri
deleted the
litellm_team_member_budget_source_reset
branch
September 21, 2026 17:34
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TLDR
Problem this solves:
How it solves it:
/team/infomemberships now carrybudget_source:team_default,custom, ornonePOST /team/{team_id}/member/{user_id}/reset_budget(proxy, org or team admin) relinks a member to the shared team budget, spend untouchednone, notteam_defaultUser Flow
Before: an admin cannot tell which members a team-wide budget change reaches, and cannot undo a member's custom budget
{"team_alias": "eng", "team_member_budget": 100}and get ateam_idback{"team_id": "...", "user_id": "bob", "max_budget_in_team": 50}budget_idand amax_budget, nothing says which ones follow the team default{"detail": "Not Found"}, and the UI has no such action, so their only option is to re-type the team value with/team/member_update, which leaves bob on a private copy that later team changes still missAfter: the same admin sees each member's budget source and can put a member back on the team default in one click
{"team_alias": "eng", "team_member_budget": 100}and get ateam_idback{"team_id": "...", "user_id": "bob", "max_budget_in_team": 50}"budget_source": "team_default"or"budget_source": "custom"(bob)"budget_source": "team_default"and the previous budget id, and works with a team admin's key as well as the proxy admin's{"team_id": "...", "team_member_budget": 10}and bob is capped at $10 like everyone else on the default, with his spend so far keptRelevant issues
Follow-up to #41347 (issue #40783)
Affected release
Linear ticket
Resolves LIT-7716
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
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@greptileaito 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
Live proxy on localhost:4000 with Postgres and one real
openai/gpt-5.4-minideployment,master_key: sk-1234. Same setup on both sides: a team withteam_member_budget: 100, two members added with no per-member budget, then one of them givenmax_budget_in_team: 50.$AUTHis-H "Authorization: Bearer sk-1234" -H "Content-Type: application/json". Admin UI screenshots of the Members tab are in the testing recording shared in the review thread rather than embedded hereBefore (cda022c)
/team/info does not say which member follows the team default
curl http://localhost:4000/team/new $AUTH -d '{"team_alias":"lit7716-followup","team_member_budget":100}'->{"team_id":"98c2cbc9-555c-4eb8-b262-bdc23426d3a6","team_member_budget_id":"team-lit7716-followup-budget-fa62d290051b4f9ba644e6b52cd466ee"}curl http://localhost:4000/team/member_add $AUTH -d '{"team_id":"98c2cbc9-...","member":[{"user_id":"lit7716-inherits-27317","role":"user"},{"user_id":"lit7716-override-27317","role":"user"}]}'-> 200curl http://localhost:4000/team/member_update $AUTH -d '{"team_id":"98c2cbc9-...","user_id":"lit7716-override-27317","max_budget_in_team":50}'->{"user_id":"lit7716-override-27317","max_budget_in_team":50}curl 'http://localhost:4000/team/info?team_id=98c2cbc9-...' $AUTH | jq '.team_memberships[] | {user_id, budget_source, budget_id, max_budget: .litellm_budget_table.max_budget, spend}'No way to put a member back on the team default
curl -X POST http://localhost:4000/team/98c2cbc9-.../member/lit7716-override-27317/reset_budget $AUTH->{"detail":"Not Found"}HTTP 404After (8bd9d35)
/team/info says which member follows the team default
curl http://localhost:4000/team/new $AUTH -d '{"team_alias":"lit7716-followup","team_member_budget":100}' | jq '{team_id, team_member_budget_id: .metadata.team_member_budget_id}'curl http://localhost:4000/team/member_add $AUTH -d '{"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","member":[{"user_id":"lit7716-inherits-21408","role":"user"},{"user_id":"lit7716-override-21408","role":"user"}]}' > /dev/nullcurl http://localhost:4000/team/member_update $AUTH -d '{"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","user_id":"lit7716-override-21408","max_budget_in_team":50}' | jq '{user_id, max_budget_in_team}'curl 'http://localhost:4000/team/info?team_id=1d9cca31-45b1-4296-970b-1c9c86e3c32a' $AUTH | jq '.team_memberships[] | {user_id, budget_source, budget_id, max_budget: .litellm_budget_table.max_budget, spend}'A member can be put back on the team default, by the proxy admin or a team admin, and then follows later /team/update changes
KEY_B=$(curl http://localhost:4000/key/generate $AUTH -d '{"team_id":"1d9cca31-...","user_id":"lit7716-override-21408"}' | jq -r .key), thencurl http://localhost:4000/v1/chat/completions -H 'Authorization: Bearer $KEY_B' ... -d '{"model":"gpt-5.4-mini","messages":[{"role":"user","content":"say hi"}],"max_tokens":5}'curl 'http://localhost:4000/team/info?team_id=1d9cca31-45b1-4296-970b-1c9c86e3c32a' $AUTH | jq '.team_memberships[] | {user_id, budget_source, budget_id, max_budget: .litellm_budget_table.max_budget, spend}'curl -X POST http://localhost:4000/team/1d9cca31-45b1-4296-970b-1c9c86e3c32a/member/lit7716-override-21408/reset_budget $AUTHcurl 'http://localhost:4000/team/info?team_id=1d9cca31-45b1-4296-970b-1c9c86e3c32a' $AUTH | jq '.team_memberships[] | {user_id, budget_source, budget_id, max_budget: .litellm_budget_table.max_budget, spend}'curl http://localhost:4000/team/member_add $AUTH -d '{"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","member":[{"user_id":"lit7716-teamadmin-21408","role":"admin"}]}' > /dev/nullcurl http://localhost:4000/team/member_update $AUTH -d '{"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","user_id":"lit7716-inherits-21408","max_budget_in_team":25}' > /dev/nullKEY_ADMIN=$(curl http://localhost:4000/key/generate $AUTH -d '{"team_id":"1d9cca31-...","user_id":"lit7716-teamadmin-21408"}' | jq -r .key), thencurl -X POST http://localhost:4000/team/1d9cca31-45b1-4296-970b-1c9c86e3c32a/member/lit7716-inherits-21408/reset_budget -H 'Authorization: Bearer $KEY_ADMIN'curl http://localhost:4000/team/update $AUTH -d '{"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","team_member_budget":0.000001}' > /dev/nullcurl 'http://localhost:4000/team/info?team_id=1d9cca31-45b1-4296-970b-1c9c86e3c32a' $AUTH | jq '.team_memberships[] | {user_id, budget_source, budget_id, max_budget: .litellm_budget_table.max_budget, spend}'curl http://localhost:4000/v1/chat/completions -H 'Authorization: Bearer $KEY_B' ... (same body)Type
🆕 New Feature
Caveats (if any)
Medium
none)custom; the reset action is the way to relink themLow
budget_sourceis derived from ids at read time, no schema changeFinal Attestation
Note
Medium Risk
Changes team membership budget links and admission-related cache invalidation; mistakes could mis-enforce caps, though scope is limited to team management APIs and mirrors existing reset-spend patterns.
Overview
Adds visibility into whether each team member uses the shared team member budget or a custom budget, and a way to revert customized members back to the team default without touching spend.
API:
GET /team/infomemberships now includebudget_source(team_default,custom, ornone), derived from the member’s budget link vs the team’steam_member_budget_id(missing or deleted default rows count asnone). NewPOST /team/{team_id}/member/{user_id}/reset_budget(same admin access as reset spend) reconnects the member to the team default budget or disconnects their budget when no usable default exists; membership spend cache is invalidated after the update.Dashboard: The Members tab shows Team default / Custom badges, a Use team default confirm flow for customized members (editors only), and refreshes team data after a successful reset.
Tests cover authz, edge cases (no default, deleted default), unit behavior, and UI flows; OpenAPI/schema and endpoint audit allowlist are updated.
Reviewed by Cursor Bugbot for commit 8bd9d35. Bugbot is set up for automated code reviews on this repo. Configure here.
Link to Devin session: https://app.devin.ai/sessions/5d1eee9e522749968ae46d78e845be94
Open in Devin Desktop: https://app.devin.ai/desktop/session/5d1eee9e522749968ae46d78e845be94?variant=devin