From 4fed9bccb87aa57ab7c0db1e4b5f1afec7530f80 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Mar 2026 20:22:46 -0700 Subject: [PATCH] Fix sidecar crash: torch circular import under PyInstaller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Exclude ctranslate2.converters from PyInstaller bundle — these modules import torch at module level causing circular import crashes, and are only needed for model conversion (never used at runtime) - Defer all heavy ML imports to first handler call instead of startup, so the sidecar can send its ready message without loading torch/whisper Co-Authored-By: Claude Opus 4.6 --- python/voice_to_notes.spec | 8 ++++- python/voice_to_notes/ipc/handlers.py | 49 +++++++++++++++++++-------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/python/voice_to_notes.spec b/python/voice_to_notes.spec index 0687bb1..17d3c3f 100644 --- a/python/voice_to_notes.spec +++ b/python/voice_to_notes.spec @@ -33,7 +33,13 @@ a = Analysis( hookspath=[], hooksconfig={}, runtime_hooks=[], - excludes=["tkinter", "test", "unittest", "pip", "setuptools"], + excludes=[ + "tkinter", "test", "unittest", "pip", "setuptools", + # ctranslate2.converters imports torch at module level and causes + # circular import crashes under PyInstaller. These modules are only + # needed for model format conversion, never for inference. + "ctranslate2.converters", + ], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, diff --git a/python/voice_to_notes/ipc/handlers.py b/python/voice_to_notes/ipc/handlers.py index dcc20ef..2e64f85 100644 --- a/python/voice_to_notes/ipc/handlers.py +++ b/python/voice_to_notes/ipc/handlers.py @@ -41,11 +41,15 @@ def ping_handler(msg: IPCMessage) -> IPCMessage: def make_transcribe_handler() -> HandlerFunc: """Create a transcription handler with a persistent TranscribeService.""" - from voice_to_notes.services.transcribe import TranscribeService, result_to_payload - - service = TranscribeService() + service = None def handler(msg: IPCMessage) -> IPCMessage: + nonlocal service + if service is None: + from voice_to_notes.services.transcribe import TranscribeService + service = TranscribeService() + from voice_to_notes.services.transcribe import result_to_payload + payload = msg.payload result = service.transcribe( request_id=msg.id, @@ -66,11 +70,15 @@ def make_transcribe_handler() -> HandlerFunc: def make_diarize_handler() -> HandlerFunc: """Create a diarization handler with a persistent DiarizeService.""" - from voice_to_notes.services.diarize import DiarizeService, diarization_to_payload - - service = DiarizeService() + service = None def handler(msg: IPCMessage) -> IPCMessage: + nonlocal service + if service is None: + from voice_to_notes.services.diarize import DiarizeService + service = DiarizeService() + from voice_to_notes.services.diarize import diarization_to_payload + payload = msg.payload result = service.diarize( request_id=msg.id, @@ -163,11 +171,15 @@ def make_diarize_download_handler() -> HandlerFunc: def make_pipeline_handler() -> HandlerFunc: """Create a full pipeline handler (transcribe + diarize + merge).""" - from voice_to_notes.services.pipeline import PipelineService, pipeline_result_to_payload - - service = PipelineService() + service = None def handler(msg: IPCMessage) -> IPCMessage: + nonlocal service + if service is None: + from voice_to_notes.services.pipeline import PipelineService + service = PipelineService() + from voice_to_notes.services.pipeline import pipeline_result_to_payload + payload = msg.payload result = service.run( request_id=msg.id, @@ -193,11 +205,15 @@ def make_pipeline_handler() -> HandlerFunc: def make_export_handler() -> HandlerFunc: """Create an export handler.""" - from voice_to_notes.services.export import ExportService, make_export_request - - service = ExportService() + service = None def handler(msg: IPCMessage) -> IPCMessage: + nonlocal service + if service is None: + from voice_to_notes.services.export import ExportService + service = ExportService() + from voice_to_notes.services.export import make_export_request + request = make_export_request(msg.payload) output_path = service.export(request) return IPCMessage( @@ -211,11 +227,14 @@ def make_export_handler() -> HandlerFunc: def make_ai_chat_handler() -> HandlerFunc: """Create an AI chat handler with persistent AIProviderService.""" - from voice_to_notes.services.ai_provider import create_default_service - - service = create_default_service() + service = None def handler(msg: IPCMessage) -> IPCMessage: + nonlocal service + if service is None: + from voice_to_notes.services.ai_provider import create_default_service + service = create_default_service() + payload = msg.payload action = payload.get("action", "chat")