Skip to content

fix(proxy): block project requests when max_budget is 0 - #41997

Merged
ryan-crabbe-berri merged 1 commit into
mainfrom
litellm_project_zero_budget_blocks
Sep 19, 2026
Merged

ryan-crabbe-berri merged 1 commit into
mainfrom
litellm_project_zero_budget_blocks

Conversation

@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

How it solves it:

  • Drop the <= 0 skip in the project max budget check
  • 0 now blocks on the first request, null stays unlimited
  • Regression tests fail if the skip ever comes back

User Flow

Before: a proxy admin creates a project with a $0 budget to stop it from spending, and its keys keep working

  1. The admin sends POST https://litellm-domain/project/new with "max_budget": 0 and gets back a project_id
  2. They send POST https://litellm-domain/key/generate with that project_id and get back a key
  3. A developer sends POST https://litellm-domain/v1/chat/completions with that key
  4. The call returns 200 with a completion, and every later call does too

After: the same project blocks on the first request, and sending null is how the admin lifts the limit

  1. The admin sends POST https://litellm-domain/project/new with "max_budget": 0 and gets back a project_id
  2. They send POST https://litellm-domain/key/generate with that project_id and get back a key
  3. A developer sends POST https://litellm-domain/v1/chat/completions with that key
  4. The call returns 429 with Budget has been exceeded! Project=<project_id> Current cost: 0.0, Max budget: 0.0
  5. The admin sends POST https://litellm-domain/project/update with "max_budget": null, and the same key returns 200 again

Relevant issues

Follow-up to #41354

Affected release

Linear ticket

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

Proxy started with litellm --config litellm/proxy/dev_config.yaml --port 4173 --use_v2_migration_resolver, master key sk-1234, real OpenAI calls to gpt-5.5. Shared shell helpers for both legs:

B=http://localhost:4173
ADMIN=(-H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json')
field() { python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('$1') or d)"; }
chat() {
  curl -s -w '\n%{http_code}' $B/v1/chat/completions -H "Authorization: Bearer $1" -H 'Content-Type: application/json' \
    -d '{"model":"gpt-5.5","max_completion_tokens":200,"messages":[{"role":"user","content":"say hi"}]}' |
    python3 -c 'import sys,json;body,code=sys.stdin.read().rsplit("\n",1);d=json.loads(body);print(code, d["choices"][0]["message"]["content"] if "choices" in d else d["error"]["message"][:160])'
}
TEAM=$(curl -s $B/team/new "${ADMIN[@]}" -d '{"team_alias":"zero-budget-qa"}' | field team_id)
setup() { # $1 = max_budget, sets PID and KEY
  PID=$(curl -s $B/project/new "${ADMIN[@]}" -d "{\"project_alias\":\"qa-$1\",\"team_id\":\"$TEAM\",\"max_budget\":$1}" | field project_id)
  KEY=$(curl -s $B/key/generate "${ADMIN[@]}" -d "{\"project_id\":\"$PID\",\"team_id\":\"$TEAM\"}" | field key)
}

Before (eda1fab)

A project with max_budget 0 blocks

  1. setup 0; chat "$KEY"
  2. Observed: 200 Hi!

A project with max_budget null is unlimited

  1. setup null; chat "$KEY"
  2. Observed: 200 Hi!

Sending max_budget null lifts a 0 budget

  1. setup 0; curl -s $B/project/update "${ADMIN[@]}" -d "{\"project_id\":\"$PID\",\"max_budget\":null}"; chat "$KEY"
  2. Observed: the update response shows max_budget: None, then 200 Hi!

After (162d622)

A project with max_budget 0 blocks

  1. setup 0; chat "$KEY"
  2. Observed: 429 Budget has been exceeded! Project=104764c1-c39e-4ae8-9b59-ab679777bf6e Current cost: 0.0, Max budget: 0.0

A project with max_budget null is unlimited

  1. setup null; chat "$KEY"
  2. Observed: 200 Hi!

Sending max_budget null lifts a 0 budget

  1. setup 0; curl -s $B/project/update "${ADMIN[@]}" -d "{\"project_id\":\"$PID\",\"max_budget\":null}"; chat "$KEY"
  2. Observed: the update response shows max_budget: None, then 200 Hi!

Type

Bug Fix

Caveats (if any)

Severe

  • Projects already saved with max_budget: 0 start returning 429

Low

  • The budget reservation step still skips a 0 budget, same as it does for keys, teams, users and orgs
    • The auth check runs first and already blocks, so nothing is left to reserve
  • A negative project budget now blocks too, like a key's does
    • /project/new and /project/update already reject negatives with a 400, so only a direct DB write can produce one
  • Team default member budgets, model access group budgets and the global max_budget still treat 0 as unlimited, and this PR does not touch them

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

A project max_budget of 0 was treated as unbudgeted by #41354, while key budgets block at 0 and null is the unlimited value. Drop the <= 0 skip so 0 blocks and null stays unlimited
@ryan-crabbe-berri
ryan-crabbe-berri requested a review from a team September 19, 2026 19:10
@greptile-apps

greptile-apps Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

The budget logic appears correct, but the explicit backward-compatibility requirement must be addressed before merging

Findings

  1. P2 Unflagged compatibility change ▶

Summary

This PR changes project budget authorization so a finite zero budget blocks requests while null remains unlimited

  • Removes the non-positive-budget exemption from the project authorization check
  • Adds focused regression coverage for zero-budget blocking and null-budget bypass
  • Updates the budget test helper to accept optional maximum budgets

Reviews (1) · Last reviewed commit: "fix(proxy): block project requests when ..."

max_budget = project_object.litellm_budget_table.max_budget

if max_budget is None or max_budget <= 0 or not math.isfinite(max_budget):
if max_budget is None or not math.isfinite(max_budget):

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 Unflagged compatibility change

Removing this exemption immediately blocks existing projects with zero budgets. This violates the repository directive to avoid backward-incompatible changes without user-controlled flags, so the requirement must be satisfied before merging

Rule Used: What: avoid backwards-incompatible changes without user-controlled flags Why: This breaks current behaviour for users using existing functionality Example of BAD: this PR (#22164) introduced run_post_custom... (source)

@codspeed

codspeed Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_project_zero_budget_blocks (162d622) with main (4301ac4)

Open in CodSpeed

@codecov

codecov Bot commented Sep 19, 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
ryan-crabbe-berri merged commit 370eacc into main Sep 19, 2026
94 checks passed
@ryan-crabbe-berri
ryan-crabbe-berri deleted the litellm_project_zero_budget_blocks branch September 19, 2026 22:24
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