OpenHands / OpenHands/enterprise

Stale LiteLLM spend under a /bin/bash team budget blocks free GLM automations

Open
#175 1 comment 0 reactions 0 assignees View on GitHub

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

  1. The original LiteLLM team accumulated approximately $1.57 of spend.

  2. The Keycloak user was deleted without deleting the corresponding OpenHands and LiteLLM records, so LiteLLM could retain that user/team and its spend.

  3. On registration, the auth callback entered the new-user branch in enterprise/server/routes/auth.py. UserStore.create_user() called create_default_settings(), which invoked LiteLlmManager.create_entries(..., create_user=True).

  4. LiteLlmManager.create_entries() initialized team_budget from DEFAULT_INITIAL_BUDGET=0.0, then queried /team/info. If it found the existing team, it replaced the default with that team's existing max_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.

  5. 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.0 before registration;
    • staging ran code predating the existing-budget preservation logic;
    • /team/info did not return the existing team, after which _create_team() encountered an existing-team conflict and _update_team() applied the $0 default;
    • another team-budget writer set the cap to $0 while preserving spend.
  6. With create_user=True, onboarding deleted and recreated the stale LiteLLM user. The LiteLLM team and its financial state remained separate.

  7. The automation read AUTOMATION_MODEL and loaded the selected profile with workspace.get_llm(). Its run log confirmed litellm_proxy/glm-5.2.

  8. GLM-5.2 has zero input, output, and cache-read cost in staging and production. LiteLLM's _team_max_budget_check() compared historical spend with max_budget without considering the model's zero configured cost and rejected the request.

  9. A Canvas conversation created during diagnosis also resolved to gpt-5.5 instead of its intended profile model. OpenHands#16342 fixes that path by resolving the effective profile model and sending it as llm_model.

Fix locations

  1. Add logging around the /team/info, /team/new, and /team/update sequence in create_entries() to record the team ID and budget before and after reconciliation.
  2. In the existing-team conflict branch of _create_team(), re-fetch the team and preserve its financial fields before calling _update_team().
  3. 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, run create_entries(create_user=True), and assert both values remain unchanged.
  4. Update _team_max_budget_check() so a model with trusted server-side zero pricing can run when paid credits are exhausted.
  5. Merge and validate OpenHands#16342.
  6. 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.