From f14020d38509d1d936110dfc584559d4e5cbe018 Mon Sep 17 00:00:00 2001 From: Luis Novo Date: Sat, 24 Jan 2026 18:19:02 -0300 Subject: [PATCH] fix: run execute_command_sync in thread pool to avoid event loop conflict (#468) When async_processing=False, the sync path calls execute_command_sync() which internally uses asyncio.run(). This fails when called from FastAPI's already-running event loop with 'asyncio.run() cannot be called from a running event loop'. Wrapping the call in asyncio.to_thread() runs it in a thread pool executor, avoiding the event loop conflict while preserving the synchronous behavior from the API consumer's perspective. Fixes #453 --- api/routers/sources.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/api/routers/sources.py b/api/routers/sources.py index 78545e8..32d32dd 100644 --- a/api/routers/sources.py +++ b/api/routers/sources.py @@ -1,3 +1,4 @@ +import asyncio import os from pathlib import Path from typing import Any, List, Optional @@ -449,11 +450,15 @@ async def create_source( embed=source_data.embed, ) - result = execute_command_sync( + # Run in thread pool to avoid blocking the event loop + # execute_command_sync uses asyncio.run() internally which can't + # be called from an already-running event loop (FastAPI) + result = await asyncio.to_thread( + execute_command_sync, "open_notebook", # app name "process_source", # command name command_input.model_dump(), - timeout=300, # 5 minute timeout for sync processing + 300, # 5 minute timeout for sync processing ) if not result.is_success():