I was building a voice assistant: local speech recognition via faster-whisper, reasoning via the Claude API, and local TTS via Windows WinRT. The kind of setup where the only data that leaves your machine is the conversation text.
Everything worked in isolation. But when I put it all together, the process died silently.
The crash
exit code 5
No Python traceback. No error message. Just... gone.
I'd seen exit code 1 before — that's a normal exception. Exit code 5 is different. On Windows, it corresponds to a native access violation (0xC0000005): something in C or C++ tried to read or write memory it doesn't own. Python never gets a chance to catch it because the crash happens below the interpreter, in native code.
The frustrating part: I couldn't reproduce it consistently by commenting things out. Sometimes it crashed. Sometimes it didn't. And I had no output to work with — even my print statements at the top of the file weren't appearing.
The first insight: unbuffered output
When Python crashes with a native exception, stdout may not be flushed. Any print() output that was buffered in Python's I/O layer disappears with the process.
The fix for debugging: run Python with -u (unbuffered):
python -u your_script.py
This forces every print() to go directly to the OS. Now I had output — not much, but enough to see that the last line printed was always inside voz.py, right after the WinRT imports.
The crash was happening at module load time.
Binary search on imports
Module-level crashes in Python are tricky. There's no stack trace, no exception, no line number. The only signal is: which module was loading when the process died?
I narrowed it down by bisection. Comment out half the imports. Run. See which half crashes. Repeat.
After a few rounds, I had isolated the culprit to two specific imports:
import ctranslate2 # faster-whisper's backend
from winrt.windows.media.speechsynthesis import SpeechSynthesizer # WinRT TTS
Each one, imported alone, worked perfectly. Together, in the wrong order, the process crashed every time.
What's actually happening
Both ctranslate2 and WinRT load native DLLs into the Python process at import time. This is standard — Python extensions (.pyd files) are just DLLs.
The problem is what those DLLs do at load time:
WinRT's COM runtime sets up thread-local and process-global state when it initializes. It makes certain assumptions about the state of the Win32 heap and thread apartments.
ctranslate2, when it initializes its CPU backend (or tries to detect GPU capabilities), also does low-level memory operations. If WinRT has already set up certain process-global state that ctranslate2 doesn't expect to find, the access violation follows.
The exact root cause would require reading the ctranslate2 and WinRT DLL source. But the empirical fix is unambiguous:
Load ctranslate2 before WinRT, and the crash never happens.
The fix
In the module where you use WinRT, force ctranslate2 to load its native libraries first — even if you don't use it in that file:
# voz.py
# IMPORTANT: ctranslate2 must be imported before any winrt module.
# Loading WinRT first causes a native crash (exit code 5, 0xC0000005)
# when ctranslate2 later tries to initialize its CPU backend.
try:
import ctranslate2 # noqa: F401
except ImportError:
pass # ctranslate2 not installed — WinRT alone works fine
from winrt.windows.media.speechsynthesis import SpeechSynthesizer
from winrt.windows.storage.streams import DataReader
# ... rest of the module
The try/except ImportError makes this safe: if ctranslate2 isn't installed, the WinRT import proceeds normally.
What didn't work
Before finding this, I tried:
-
Adding explicit
import ctranslate2at the top of the entry point — not enough; the order within each module still matters - Wrapping WinRT in a try/except — the crash bypasses Python's exception handling entirely
- Running in a subprocess — deferred the crash, didn't prevent it
- Checking for version conflicts — no version combination fixed it; it's a load-order issue, not a version issue
How to know if this is your bug
- You're using
faster-whisper(or anything that importsctranslate2) and WinRT speech synthesis in the same Python process on Windows - The process exits with code 5, or silently with no output
- The crash disappears when you remove one of the two components
- Standard Python debugging tools show nothing
The fix: in whichever file imports WinRT first, add import ctranslate2 before the WinRT imports.
The project
This came up while building Gémeo Conselheiro — a voice AI assistant where your laptop does all the heavy lifting: local STT via faster-whisper, local TTS via WinRT (Microsoft Hélia, pt-PT), and only the conversation text goes to the Claude API. There's also a phone call mode that connects your phone over Wi-Fi for hands-free conversation.
If you're building a similar local-first voice AI stack on Windows and hit this, I hope this saves you the time I spent on it.
Top comments (0)