Implementation:OpenHands OpenHands UserStore Migrate User
| Knowledge Sources | |
|---|---|
| Domains | Organization_Management, Multi_Tenancy |
| Last Updated | 2026-02-11 21:00 GMT |
Overview
Concrete tool for migrating legacy user data into the organizational structure provided by the OpenHands enterprise storage layer.
Description
UserStore.migrate_user performs a comprehensive migration of an existing user from the legacy flat data model to the multi-tenant organizational model. The method orchestrates the following sequence of operations:
- Personal organization creation: Creates a personal organization for the user using the full organization onboarding pipeline (name validation, LiteLLM provisioning, entity construction, ownership establishment, persistence).
- LiteLLM entry migration: Invokes
LiteLlmManager.migrate_entriesto transfer the user's existing LLM proxy teams, keys, and model access records from user-scoped to organization-scoped configuration. - Stripe customer migration: Invokes
stripe_service.migrate_customerto reassociate the user's Stripe customer record, subscriptions, and payment methods with the new personal organization. - User record creation: Constructs and persists the User entity linked to the newly created personal organization.
This method is designed to be called during the first authenticated request from a legacy user, enabling just-in-time migration without requiring a batch migration job.
Usage
Call this method when a legacy user (one without an existing User record in the new schema) authenticates for the first time. The method is typically invoked from the user resolution middleware or the login flow when the system detects that the authenticated Keycloak user does not yet have a corresponding User record.
Code Reference
Source Location
- Repository: OpenHands
- File:
enterprise/storage/user_store.py - Lines: L157-371
Signature
async def migrate_user(
self,
user_id: str,
user_settings: UserSettings,
user_info: dict,
) -> User:
"""Migrate a legacy user to the organizational data model.
Creates a personal organization, migrates LiteLLM entries and Stripe
billing state, and constructs the User record.
Args:
user_id: The Keycloak user ID.
user_settings: The user's existing settings to preserve.
user_info: Dictionary containing user profile data (username, email, etc.).
Returns:
The newly created User entity linked to a personal organization.
"""
Import
from enterprise.storage.user_store import UserStore
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| user_id | str | Yes | The Keycloak user ID of the legacy user to migrate |
| user_settings | UserSettings | Yes | The user's existing settings object to preserve during migration |
| user_info | dict | Yes | Dictionary containing user profile data including username, email, and other identity attributes from Keycloak |
Outputs
| Name | Type | Description |
|---|---|---|
| user | User | The newly created User entity, linked to a personal organization with migrated LiteLLM and Stripe state |
Usage Examples
Basic Usage
from enterprise.storage.user_store import UserStore
user_store = UserStore(session=db_session)
# Called during first login of a legacy user
user = await user_store.migrate_user(
user_id="keycloak-user-abc123",
user_settings=UserSettings(
theme="dark",
default_model="gpt-4",
language="en",
),
user_info={
"username": "janedoe",
"email": "jane@example.com",
"name": "Jane Doe",
},
)
# user.personal_org_id now references the newly created personal organization
# LiteLLM entries and Stripe customer have been migrated to the org scope