Skip to content

fix(budgets): prevent per-user allowance renewal on sync - #205

Merged
tofarr merged 11 commits into
mainfrom
fix/user-budget-cycle-baselines
Aug 20, 2026
Merged

fix(budgets): prevent per-user allowance renewal on sync#205
tofarr merged 11 commits into
mainfrom
fix/user-budget-cycle-baselines

Conversation

@saurya

@saurya saurya commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • persist each capped member's cumulative LiteLLM spend baseline for the active organization budget cycle
  • derive max_budget_in_team from the persisted baseline plus the effective monthly allowance instead of current spend plus allowance on every synchronization
  • clear member baselines when a new cycle starts and prune entries for members that are no longer actively capped
  • add migration 149 and cross-session regression coverage

Incident context

This fixes the per-user sliding-cap issue identified during the Fujitsu budget incident. LiteLLM compares cumulative membership spend to an absolute max_budget_in_team; recalculating that cap from current spend every 15 minutes effectively renewed the member's full allowance on every maintenance run.

After this change, a member observed at $7 cumulative spend with a $50 allowance receives an absolute cap of $57. A later sync at $23 spend keeps the cap at $57 rather than moving it to $73.

Stack

Rollout behavior

Existing deployments do not retain historical per-member cycle-start LiteLLM spend. Therefore the first successful synchronization after this migration bootstraps each current member's baseline from their current cumulative membership spend. This can grant one fresh configured allowance at rollout, but subsequent 15-minute synchronizations remain anchored and cannot renew it again. New members and newly capped members are similarly anchored when first governed during a cycle.

Validation

  • 216 passed across affected billing, LiteLLM, organization-budget, and organization-service unit suites
  • 15 passed in focused organization-budget service tests
  • enterprise migration static integrity passed
  • PostgreSQL 16 full-chain upgrade and revision 149 downgrade/re-upgrade passed with both pg8000 and psycopg2
  • Ruff, formatting, and mypy passed

This pull request was created by an AI agent (OpenHands) on behalf of Saurya Velagapudi.


Enterprise server image for this PR:

ghcr.io/openhands/enterprise-server:sha-9da495c

saurya and others added 10 commits August 11, 2026 18:03
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Renumber the personal organization budget cleanup migration to follow the current revision 147 head and update its regression-test import.

Co-authored-by: openhands <openhands@all-hands.dev>
Persist each governed member's cumulative LiteLLM spend at cycle start and derive their absolute cap from that stable baseline instead of current spend on every sync.

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions github-actions Bot added the type: fix A bug fix label Aug 19, 2026
@github-actions

Copy link
Copy Markdown

⚠️ This PR contains migrations. Please synchronize before merging to prevent conflicts.

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  enterprise/server/services
  org_budget_service.py 229-233, 344-350, 657-662, 667-678, 698-702
  enterprise/storage
  org_budget_settings.py
Project Total  

This report was generated by python-coverage-comment-action

@saurya
saurya marked this pull request as ready for review August 19, 2026 20:10
@saurya
saurya requested a review from ak684 August 19, 2026 20:34
Base automatically changed from fix/personal-org-billing-credits to main August 20, 2026 13:30
Resolve merge conflict in enterprise/tests/unit/test_org_budget_service.py
by combining both branches' new tests:
- main's test_sync_litellm_budgets_skips_passive_disabled_team_org (from #167)
- this PR's test_sync_litellm_budgets_keeps_member_cap_stable_across_sessions

Co-authored-by: openhands <openhands@all-hands.dev>
@tofarr

tofarr commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Merge conflict resolved

Resolved the conflict in enterprise/tests/unit/test_org_budget_service.py caused by both main (PR #167) and this PR adding new tests at the same location.

The resolution keeps both tests:

enterprise/server/services/org_budget_service.py auto-merged cleanly. The migration chain remains intact (147 → 148 from #167 → 149 from this PR), with a single Alembic head.

Verified locally:

  • 15 passed in tests/unit/test_org_budget_service.py
  • Enterprise pre-commit (ruff, ruff-format, mypy) passed on the touched files
  • Alembic heads reports a single head at revision 149

This comment was posted by an AI agent (OpenHands) on behalf of Saurya Velagapudi.

@tofarr
tofarr enabled auto-merge (squash) August 20, 2026 14:01
@tofarr
tofarr merged commit f049838 into main Aug 20, 2026
21 checks passed
@tofarr
tofarr deleted the fix/user-budget-cycle-baselines branch August 20, 2026 14:04
@openhands-release-bot openhands-release-bot Bot added the released: 1.55.0 Shipped in 1.55.0 label Aug 25, 2026
@openhands-release-bot

Copy link
Copy Markdown

🚀 Released in 1.55.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released: 1.55.0 Shipped in 1.55.0 type: fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants