Skip to content

Commit 0023eb0

Browse files
(Hotfix): Handle cases where user secrets store doesn't exist (OpenHands#8745)
Co-authored-by: openhands <[email protected]>
1 parent c3ab4b4 commit 0023eb0

File tree

2 files changed

+53
-24
lines changed

2 files changed

+53
-24
lines changed

openhands/server/routes/secrets.py

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,7 @@ async def load_custom_secrets_names(
188188
) -> GETCustomSecrets | JSONResponse:
189189
try:
190190
if not user_secrets:
191-
return JSONResponse(
192-
status_code=status.HTTP_404_NOT_FOUND,
193-
content={'error': 'User secrets not found'},
194-
)
191+
return GETCustomSecrets(custom_secrets=[])
195192

196193
custom_secrets: list[CustomSecretWithoutValueModel] = []
197194
if user_secrets.custom_secrets:
@@ -220,31 +217,30 @@ async def create_custom_secret(
220217
) -> JSONResponse:
221218
try:
222219
existing_secrets = await secrets_store.load()
223-
if existing_secrets:
224-
custom_secrets = dict(existing_secrets.custom_secrets)
225-
226-
secret_name = incoming_secret.name
227-
secret_value = incoming_secret.value
228-
secret_description = incoming_secret.description
220+
custom_secrets = dict(existing_secrets.custom_secrets) if existing_secrets else {}
229221

230-
if secret_name in custom_secrets:
231-
return JSONResponse(
232-
status_code=status.HTTP_400_BAD_REQUEST,
233-
content={'message': f'Secret {secret_name} already exists'},
234-
)
222+
secret_name = incoming_secret.name
223+
secret_value = incoming_secret.value
224+
secret_description = incoming_secret.description
235225

236-
custom_secrets[secret_name] = CustomSecret(
237-
secret=secret_value,
238-
description=secret_description or '',
226+
if secret_name in custom_secrets:
227+
return JSONResponse(
228+
status_code=status.HTTP_400_BAD_REQUEST,
229+
content={'message': f'Secret {secret_name} already exists'},
239230
)
240231

241-
# Create a new UserSecrets that preserves provider tokens
242-
updated_user_secrets = UserSecrets(
243-
custom_secrets=custom_secrets,
244-
provider_tokens=existing_secrets.provider_tokens,
245-
)
232+
custom_secrets[secret_name] = CustomSecret(
233+
secret=secret_value,
234+
description=secret_description or '',
235+
)
236+
237+
# Create a new UserSecrets that preserves provider tokens
238+
updated_user_secrets = UserSecrets(
239+
custom_secrets=custom_secrets,
240+
provider_tokens=existing_secrets.provider_tokens if existing_secrets else {},
241+
)
246242

247-
await secrets_store.store(updated_user_secrets)
243+
await secrets_store.store(updated_user_secrets)
248244

249245
return JSONResponse(
250246
status_code=status.HTTP_201_CREATED,

tests/unit/test_secrets_api.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,39 @@ async def test_add_custom_secret(test_client, file_secrets_store):
138138
)
139139

140140

141+
@pytest.mark.asyncio
142+
async def test_create_custom_secret_with_no_existing_secrets(
143+
test_client, file_secrets_store
144+
):
145+
"""Test creating a custom secret when there are no existing secrets at all."""
146+
147+
# Don't store any initial settings - this simulates a completely new user
148+
# or a situation where the secrets store is empty
149+
150+
# Make the POST request to add a custom secret
151+
add_secret_data = {
152+
'name': 'NEW_API_KEY',
153+
'value': 'new-api-key-value',
154+
'description': 'Test API Key',
155+
}
156+
response = test_client.post('/api/secrets', json=add_secret_data)
157+
assert response.status_code == 201
158+
159+
# Verify that the settings were stored with the new secret
160+
stored_settings = await file_secrets_store.load()
161+
162+
# Check that the secret was added
163+
assert 'NEW_API_KEY' in stored_settings.custom_secrets
164+
assert (
165+
stored_settings.custom_secrets['NEW_API_KEY'].secret.get_secret_value()
166+
== 'new-api-key-value'
167+
)
168+
assert stored_settings.custom_secrets['NEW_API_KEY'].description == 'Test API Key'
169+
170+
# Check that provider_tokens is an empty dict, not None
171+
assert stored_settings.provider_tokens == {}
172+
173+
141174
@pytest.mark.asyncio
142175
async def test_update_existing_custom_secret(test_client, file_secrets_store):
143176
"""Test updating an existing custom secret's name and description (cannot change value once set)."""

0 commit comments

Comments
 (0)