Implementation:OpenHands OpenHands OrgService Persist With Compensation
| Knowledge Sources | |
|---|---|
| Domains | Organization_Management, Multi_Tenancy |
| Last Updated | 2026-02-11 21:00 GMT |
Overview
Concrete tool for persisting organization and membership entities with compensating cleanup of LiteLLM resources on failure provided by the OpenHands enterprise storage layer.
Description
OrgService._persist_with_compensation attempts to add the Org and OrgMember entities to the database session and commit the transaction. If the commit fails for any reason (constraint violation, connection error, etc.), the method:
- Rolls back the database transaction.
- Invokes _cleanup_litellm_resources (at L363-397) to delete the previously provisioned LiteLLM team and API key associated with the organization.
- Re-raises the original exception so the caller can handle the failure appropriately.
The _cleanup_litellm_resources method is a best-effort compensating action. It attempts to remove the LiteLLM team and revoke the API key via the LiteLLM proxy API. If the cleanup itself encounters an error, it returns the exception (rather than raising it) so that the original failure is not masked.
This method is private (prefixed with underscore) because it is an internal pipeline step, not intended to be called directly by external consumers.
Usage
This method is called as the final step of the organization creation pipeline, after entity construction and ownership establishment. It should only be invoked when both the Org and OrgMember entities are fully initialized and LiteLLM resources have been provisioned.
Code Reference
Source Location
- Repository: OpenHands
- File:
enterprise/storage/org_service.py - Lines: L282-320 (_persist_with_compensation), L363-397 (_cleanup_litellm_resources)
Signature
async def _persist_with_compensation(
self,
org: Org,
org_member: OrgMember,
org_id: UUID,
user_id: str,
) -> Org:
"""Persist org and member entities, cleaning up LiteLLM on failure.
Args:
org: The fully constructed Org entity.
org_member: The fully constructed OrgMember entity.
org_id: The organization UUID (for LiteLLM cleanup on failure).
user_id: The user ID (for LiteLLM cleanup on failure).
Returns:
The persisted Org entity on success.
Raises:
Exception: Re-raises the original database error after compensation.
"""
async def _cleanup_litellm_resources(
self,
org_id: UUID,
user_id: str,
) -> Exception | None:
"""Best-effort cleanup of provisioned LiteLLM team and API key.
Args:
org_id: The organization UUID whose LiteLLM team should be deleted.
user_id: The user ID whose LiteLLM key should be revoked.
Returns:
None on successful cleanup, or the Exception encountered during cleanup.
"""
Import
from enterprise.storage.org_service import OrgService
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| org | Org | Yes | The fully constructed organization entity to persist |
| org_member | OrgMember | Yes | The fully constructed membership entity to persist |
| org_id | UUID | Yes | The organization UUID, used for LiteLLM cleanup on failure |
| user_id | str | Yes | The user ID, used for LiteLLM key cleanup on failure |
Outputs
| Name | Type | Description |
|---|---|---|
| org | Org | The persisted Org entity (with database-assigned fields populated) on success |
Usage Examples
Basic Usage
from enterprise.storage.org_service import OrgService
org_service = OrgService(session=db_session)
# After entity construction and LiteLLM provisioning...
try:
persisted_org = await org_service._persist_with_compensation(
org=org,
org_member=org_member,
org_id=org_id,
user_id=user_id,
)
# Success — org and org_member are now in the database
except Exception as e:
# Database commit failed; LiteLLM resources have been cleaned up
logger.error(f"Organization creation failed: {e}")
raise