+ {hasDuplicatedEmail && (
+
+ {t(I18nKey.AUTH$DUPLICATE_EMAIL_ERROR)}
+
+ )}
{t(I18nKey.AUTH$SIGN_IN_WITH_IDENTITY_PROVIDER)}
diff --git a/frontend/src/i18n/declaration.ts b/frontend/src/i18n/declaration.ts
index 1b330730d9..0dd668cacc 100644
--- a/frontend/src/i18n/declaration.ts
+++ b/frontend/src/i18n/declaration.ts
@@ -730,6 +730,7 @@ export enum I18nKey {
MICROAGENT_MANAGEMENT$USE_MICROAGENTS = "MICROAGENT_MANAGEMENT$USE_MICROAGENTS",
AUTH$BY_SIGNING_UP_YOU_AGREE_TO_OUR = "AUTH$BY_SIGNING_UP_YOU_AGREE_TO_OUR",
AUTH$NO_PROVIDERS_CONFIGURED = "AUTH$NO_PROVIDERS_CONFIGURED",
+ AUTH$DUPLICATE_EMAIL_ERROR = "AUTH$DUPLICATE_EMAIL_ERROR",
COMMON$TERMS_OF_SERVICE = "COMMON$TERMS_OF_SERVICE",
COMMON$AND = "COMMON$AND",
COMMON$PRIVACY_POLICY = "COMMON$PRIVACY_POLICY",
diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json
index a421de5ddf..2950b3ab72 100644
--- a/frontend/src/i18n/translation.json
+++ b/frontend/src/i18n/translation.json
@@ -11679,6 +11679,22 @@
"de": "Mindestens ein Identitätsanbieter muss konfiguriert werden (z.B. GitHub)",
"uk": "Принаймні один постачальник ідентифікації має бути налаштований (наприклад, GitHub)"
},
+ "AUTH$DUPLICATE_EMAIL_ERROR": {
+ "en": "Your account is unable to be created. Please use a different login or try again.",
+ "ja": "アカウントを作成できません。別のログインを使用するか、もう一度お試しください。",
+ "zh-CN": "无法创建您的账户。请使用其他登录方式或重试。",
+ "zh-TW": "無法建立您的帳戶。請使用其他登入方式或重試。",
+ "ko-KR": "계정을 생성할 수 없습니다. 다른 로그인을 사용하거나 다시 시도해 주세요.",
+ "no": "Kontoen din kan ikke opprettes. Vennligst bruk en annen innlogging eller prøv igjen.",
+ "it": "Impossibile creare il tuo account. Utilizza un altro accesso o riprova.",
+ "pt": "Não foi possível criar sua conta. Use um login diferente ou tente novamente.",
+ "es": "No se puede crear su cuenta. Utilice un inicio de sesión diferente o inténtelo de nuevo.",
+ "ar": "لا يمكن إنشاء حسابك. يرجى استخدام تسجيل دخول مختلف أو المحاولة مرة أخرى.",
+ "fr": "Votre compte ne peut pas être créé. Veuillez utiliser une autre connexion ou réessayer.",
+ "tr": "Hesabınız oluşturulamadı. Lütfen farklı bir giriş kullanın veya tekrar deneyin.",
+ "de": "Ihr Konto kann nicht erstellt werden. Bitte verwenden Sie eine andere Anmeldung oder versuchen Sie es erneut.",
+ "uk": "Ваш обліковий запис не може бути створений. Будь ласка, використовуйте інший спосіб входу або спробуйте ще раз."
+ },
"COMMON$TERMS_OF_SERVICE": {
"en": "Terms of Service",
"ja": "利用規約",
From 8d0e7a92b8ae61b83902324671e70d7520ee52cc Mon Sep 17 00:00:00 2001
From: Tim O'Farrell
Date: Tue, 23 Dec 2025 12:02:56 -0700
Subject: [PATCH 2/2] ALL-4636 Resolution for connection leaks (#12144)
Co-authored-by: openhands
---
enterprise/server/auth/saas_user_auth.py | 6 +++--
.../server/routes/manage_conversations.py | 9 +++----
.../server/data_models/test_conversation.py | 24 +++++++++++++++++++
3 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/enterprise/server/auth/saas_user_auth.py b/enterprise/server/auth/saas_user_auth.py
index b51d336997..73a7217fd2 100644
--- a/enterprise/server/auth/saas_user_auth.py
+++ b/enterprise/server/auth/saas_user_auth.py
@@ -154,8 +154,10 @@ class SaasUserAuth(UserAuth):
try:
# TODO: I think we can do this in a single request if we refactor
with session_maker() as session:
- tokens = session.query(AuthTokens).where(
- AuthTokens.keycloak_user_id == self.user_id
+ tokens = (
+ session.query(AuthTokens)
+ .where(AuthTokens.keycloak_user_id == self.user_id)
+ .all()
)
for token in tokens:
diff --git a/openhands/server/routes/manage_conversations.py b/openhands/server/routes/manage_conversations.py
index babbc48654..1793b07e7d 100644
--- a/openhands/server/routes/manage_conversations.py
+++ b/openhands/server/routes/manage_conversations.py
@@ -510,6 +510,10 @@ async def delete_conversation(
if v1_result is not None:
return v1_result
+ # Close connections
+ await db_session.close()
+ await httpx_client.aclose()
+
# V0 conversation logic
return await _delete_v0_conversation(conversation_id, user_id)
@@ -551,11 +555,8 @@ async def _try_delete_v1_conversation(
httpx_client,
)
)
- except (ValueError, TypeError):
- # Not a valid UUID, continue with V0 logic
- pass
except Exception:
- # Some other error, continue with V0 logic
+ # Continue with V0 logic
pass
return result
diff --git a/tests/unit/server/data_models/test_conversation.py b/tests/unit/server/data_models/test_conversation.py
index 79ff91fa7f..d5e289ecfa 100644
--- a/tests/unit/server/data_models/test_conversation.py
+++ b/tests/unit/server/data_models/test_conversation.py
@@ -946,6 +946,10 @@ async def test_delete_conversation():
# Create a mock sandbox service
mock_sandbox_service = MagicMock()
+ # Create mock db_session and httpx_client
+ mock_db_session = AsyncMock()
+ mock_httpx_client = AsyncMock()
+
# Mock the conversation manager
with patch(
'openhands.server.routes.manage_conversations.conversation_manager'
@@ -969,6 +973,8 @@ async def test_delete_conversation():
app_conversation_service=mock_app_conversation_service,
app_conversation_info_service=mock_app_conversation_info_service,
sandbox_service=mock_sandbox_service,
+ db_session=mock_db_session,
+ httpx_client=mock_httpx_client,
)
# Verify the result
@@ -1090,6 +1096,10 @@ async def test_delete_v1_conversation_not_found():
)
mock_service.delete_app_conversation = AsyncMock(return_value=False)
+ # Create mock db_session and httpx_client
+ mock_db_session = AsyncMock()
+ mock_httpx_client = AsyncMock()
+
# Call delete_conversation with V1 conversation ID
result = await delete_conversation(
request=MagicMock(),
@@ -1098,6 +1108,8 @@ async def test_delete_v1_conversation_not_found():
app_conversation_service=mock_service,
app_conversation_info_service=mock_info_service,
sandbox_service=mock_sandbox_service,
+ db_session=mock_db_session,
+ httpx_client=mock_httpx_client,
)
# Verify the result
@@ -1171,6 +1183,10 @@ async def test_delete_v1_conversation_invalid_uuid():
mock_sandbox_service = MagicMock()
mock_sandbox_service_dep.return_value = mock_sandbox_service
+ # Create mock db_session and httpx_client
+ mock_db_session = AsyncMock()
+ mock_httpx_client = AsyncMock()
+
# Call delete_conversation
result = await delete_conversation(
request=MagicMock(),
@@ -1179,6 +1195,8 @@ async def test_delete_v1_conversation_invalid_uuid():
app_conversation_service=mock_service,
app_conversation_info_service=mock_info_service,
sandbox_service=mock_sandbox_service,
+ db_session=mock_db_session,
+ httpx_client=mock_httpx_client,
)
# Verify the result
@@ -1264,6 +1282,10 @@ async def test_delete_v1_conversation_service_error():
mock_runtime_cls.delete = AsyncMock()
mock_get_runtime_cls.return_value = mock_runtime_cls
+ # Create mock db_session and httpx_client
+ mock_db_session = AsyncMock()
+ mock_httpx_client = AsyncMock()
+
# Call delete_conversation
result = await delete_conversation(
request=MagicMock(),
@@ -1272,6 +1294,8 @@ async def test_delete_v1_conversation_service_error():
app_conversation_service=mock_service,
app_conversation_info_service=mock_info_service,
sandbox_service=mock_sandbox_service,
+ db_session=mock_db_session,
+ httpx_client=mock_httpx_client,
)
# Verify the result (should fallback to V0)