OpenHands / OpenHands/enterprise
Stale LiteLLM spend under a /bin/bash team budget blocks free GLM automations
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 4
- Forks
- 2
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 101
Description
Summary
A staging user recreated after a Keycloak-only deletion had this LiteLLM team state:
spend = 1.566280244
max_budget = 0.0
Their automation correctly selected free litellm_proxy/glm-5.2, but LiteLLM rejected its first call because historical team spend exceeded the team budget.
Sequential code path
-
The original LiteLLM team accumulated approximately
$1.57of spend. -
The Keycloak user was deleted without deleting the corresponding OpenHands and LiteLLM records, so LiteLLM could retain that user/team and its spend.
-
On registration, the auth callback entered the new-user branch in
enterprise/server/routes/auth.py.UserStore.create_user()calledcreate_default_settings(), which invokedLiteLlmManager.create_entries(..., create_user=True). -
LiteLlmManager.create_entries()initializedteam_budgetfromDEFAULT_INITIAL_BUDGET=0.0, then queried/team/info. If it found the existing team, it replaced the default with that team's existingmax_budget. Therefore, the current code preserves a positive existing budget on the normal path and does not by itself explain the observed transition to$0. -
Before the automation ran, the team nevertheless reached
spend≈1.57, max_budget=0.0. The transition must be identified from deployment and LiteLLM logs. The relevant possibilities are:- the surviving team already had
max_budget=0.0before registration; - staging ran code predating the existing-budget preservation logic;
/team/infodid not return the existing team, after which_create_team()encountered an existing-team conflict and_update_team()applied the$0default;- another team-budget writer set the cap to
$0while preserving spend.
- the surviving team already had
-
With
create_user=True, onboarding deleted and recreated the stale LiteLLM user. The LiteLLM team and its financial state remained separate. -
The automation read
AUTOMATION_MODELand loaded the selected profile withworkspace.get_llm(). Its run log confirmedlitellm_proxy/glm-5.2. -
GLM-5.2 has zero input, output, and cache-read cost in staging and production. LiteLLM's
_team_max_budget_check()compared historical spend withmax_budgetwithout considering the model's zero configured cost and rejected the request. -
A Canvas conversation created during diagnosis also resolved to
gpt-5.5instead of its intended profile model. OpenHands#16342 fixes that path by resolving the effective profile model and sending it asllm_model.
Fix locations
- Add logging around the
/team/info,/team/new, and/team/updatesequence increate_entries()to record the team ID and budget before and after reconciliation. - In the existing-team conflict branch of
_create_team(), re-fetch the team and preserve its financial fields before calling_update_team(). - Add a combined regression test alongside the separate existing-team budget test and stale-user reset test: start with
max_budget=10, spend=1.57, runcreate_entries(create_user=True), and assert both values remain unchanged. - Update
_team_max_budget_check()so a model with trusted server-side zero pricing can run when paid credits are exhausted. - Merge and validate OpenHands#16342.
- Make staging account reset delete the user's Keycloak, OpenHands, and LiteLLM state together.
This issue was created by an OpenHands AI agent on behalf of Graham Neubig.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in enterprise/storage/lite_llm_manager.py with create_entries(), _create_team(), and _update_team(), then read the existing-team budget and stale-user reset tests in enterprise/tests/unit/test_lite_llm_manager.py. Reproduce create_entries(create_user=True) with max_budget=10 and spend=1.57, and inspect deployment and LiteLLM logs for the budget transition. Done means reconciliation preserves both financial values, the sequence is observable, and the regression test passes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- authentication, backend, testing-qa
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100