From 2299bd51bad70a31e9dccbb128c2253113be144f Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Sun, 24 May 2026 18:29:03 +0000 Subject: [PATCH 1/2] fix: handle already-serialized mount dicts in profile update When FastAPI parses the request body and model_dump() is called, nested MountItem models are already serialized to plain dicts. The update handler was unconditionally calling model_dump() again, causing AttributeError on dict objects. Fix: Check if mount items are already dicts before calling model_dump(). Fixes: 422 error when updating profiles with mounts. --- apps/api/src/api/config_profiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/api/config_profiles.py b/apps/api/src/api/config_profiles.py index 97d8907..08eef3b 100644 --- a/apps/api/src/api/config_profiles.py +++ b/apps/api/src/api/config_profiles.py @@ -429,7 +429,7 @@ async def update_config_profile( if field_name in ("project_id", "tool_type_id"): value = uuid.UUID(value) if value else None elif field_name == "mounts" and value is not None: - value = [m.model_dump() for m in value] + value = [m.model_dump() if not isinstance(m, dict) else m for m in value] setattr(profile, field_name, value) await session.commit() From c8da0ab6c41ca756e5c1d67da6c1db3e02bedb68 Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Sun, 24 May 2026 18:33:25 +0000 Subject: [PATCH 2/2] fix: retry start/restart on network errors When Docker starts a container, it creates network interfaces which triggers Chrome's ERR_NETWORK_CHANGED error, aborting the request. The backend successfully starts the container but the frontend never gets the response, showing 'failed to create session' even though the session is up. Fix: Add retry with exponential backoff for startInstance and restartInstance when network errors occur (no HTTP response). Retries up to 2 times with 1.5s delay between attempts. Fixes: False 'failed to create session' errors when launching tools. --- apps/web/src/api/sessions.ts | 44 ++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/apps/web/src/api/sessions.ts b/apps/web/src/api/sessions.ts index 6fe0ae4..90bd206 100644 --- a/apps/web/src/api/sessions.ts +++ b/apps/web/src/api/sessions.ts @@ -71,13 +71,23 @@ export async function startInstance( projectId: string, repoId: string, instanceId: string, - configProfileId?: string + configProfileId?: string, + retries = 2 ): Promise<{ status: string; url?: string }> { - const response = await apiClient.post( - `/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/start`, - { config_profile_id: configProfileId } - ); - return response.data; + try { + const response = await apiClient.post( + `/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/start`, + { config_profile_id: configProfileId } + ); + return response.data; + } catch (error: any) { + // Retry on network errors (e.g. Docker creating network interfaces) + if (retries > 0 && !error.response) { + await new Promise((r) => setTimeout(r, 1500)); + return startInstance(projectId, repoId, instanceId, configProfileId, retries - 1); + } + throw error; + } } export async function stopInstance( @@ -95,13 +105,23 @@ export async function restartInstance( projectId: string, repoId: string, instanceId: string, - configProfileId?: string + configProfileId?: string, + retries = 2 ): Promise<{ status: string; url?: string }> { - const response = await apiClient.post( - `/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/restart`, - { config_profile_id: configProfileId } - ); - return response.data; + try { + const response = await apiClient.post( + `/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/restart`, + { config_profile_id: configProfileId } + ); + return response.data; + } catch (error: any) { + // Retry on network errors (e.g. Docker creating network interfaces) + if (retries > 0 && !error.response) { + await new Promise((r) => setTimeout(r, 1500)); + return restartInstance(projectId, repoId, instanceId, configProfileId, retries - 1); + } + throw error; + } } export async function deleteInstance(