Crash: dictation hotkey listener calls TSMGetInputSourceProperty off main thread (EXC_BREAKPOINT in voice-hotkey thread)
Summary
OpenHuman 0.54.0 crashes on macOS 26.4.1 (Apple Silicon) within ~90 seconds of launch whenever [dictation] enabled = true. The crash fires on any key press once the dictation hotkey listener is armed. Root cause is rdev::macos::keyboard::Keyboard::string_from_code calling TSMGetInputSourceProperty from a background thread; macOS 26 enforces that this API runs on the main dispatch queue and aborts the process via dispatch_assert_queue_fail when it does not.
Environment
- App: OpenHuman 0.54.0 (CFBundle 0.54.0)
- macOS: 26.4.1 (build 25E253), Apple Silicon (Mac17,9 / M5 Pro)
- Config:
[dictation] enabled = true, hotkey = "Fn", activation_mode = "push"
- STT:
stt_provider = "whisper", whisper_in_process = true, stt_model_id = "ggml-small.en-q5_1.bin" (locally downloaded)
- Voice server:
[voice_server] auto_start = false (dormant; not involved)
- Screen intelligence:
enabled = false (also confirmed not involved)
Reproduction
- Edit
~/.openhuman/users/<id>/config.toml: set [dictation] enabled = true
- Launch OpenHuman
- Press any key on the keyboard within ~90 seconds of launch
- App terminates with
SIGTRAP (EXC_BREAKPOINT)
100% reproducible on the test system. Setting [dictation] enabled = false eliminates the crash.
Crash signature
Thread 64 Crashed:: voice-hotkey
0 libdispatch.dylib _dispatch_assert_queue_fail + 120
1 libdispatch.dylib dispatch_assert_queue$V2.cold.1 + 116
2 libdispatch.dylib dispatch_assert_queue + 108
3 HIToolbox islGetInputSourceListWithAdditions + 160
4 HIToolbox isValidateInputSourceRef + 88
5 HIToolbox TSMGetInputSourceProperty + 36
6 OpenHuman rdev::macos::keyboard::Keyboard::string_from_code + 64
7 OpenHuman rdev::macos::listen::raw_callback + 492
8 SkyLight processEventTapData + 556
...
17 OpenHuman rdev::macos::listen::listen + 284
Exception: EXC_BREAKPOINT (SIGTRAP), codes 0x1, 0x18c7d64fc (the brk 1 inside _dispatch_assert_queue_fail).
Termination reason: Namespace SIGNAL, Code 5, Trace/BPT trap: 5.
Root cause
rdev (Narsil/rdev) registers a Quartz event tap and invokes a Rust callback (raw_callback) on the SkyLight event-dispatch thread. Inside that callback, Keyboard::string_from_code calls TISCopyCurrentKeyboardInputSource / TSMGetInputSourceProperty to resolve the keycode to a printable string. As of recent macOS releases (definitely 26.x), the TextInputSources API contains dispatch_assert_queue(dispatch_get_main_queue()) and the process is killed when invoked off the main thread.
This is a well-known rdev limitation; see for example rdev issues tagged "macOS" / "thread" upstream.
Suggested fixes (in order of preference)
- Replace rdev with
tauri-plugin-global-shortcut or global-hotkey for the dictation listener. Both crates dispatch the hotkey callback on the Tauri/main thread and avoid the off-main TSM call entirely.
- Cache keycode→string mappings at startup on the main thread and look up from the rdev callback thread. The keyboard layout rarely changes during a session; invalidate the cache on
kTISNotifySelectedKeyboardInputSourceChanged (delivered on the main run loop).
- Wrap the TSM call in
dispatch_sync(dispatch_get_main_queue(), ...) inside rdev (or in OpenHuman's rdev integration shim). This is the smallest patch but adds latency to every key event.
- Upstream a fix to rdev if no maintained fork already has one.
Option 1 is the most robust and removes a dependency that has caused multiple cross-platform headaches historically.
Workaround
Set [dictation] enabled = false in config.toml. The app launches and runs normally; only the global dictation hotkey is lost. In-app voice surfaces appear unaffected.
Attached artifacts
- Full
.ips crash report (from ~/Library/Logs/DiagnosticReports/OpenHuman-2026-05-26-023634.ips) — available on request, redacted of system identifiers.
- Sanitized
config.toml showing the triggering setting — available on request.
Why this matters
[dictation] enabled = true is the documented path for the headline voice feature. Until this is fixed, every macOS 26 user who enables dictation hits a crash loop within seconds of launch, with no in-app indication of cause (logs show clean dictation startup, then process exit). The user-facing impact is "app quits unexpectedly" with no actionable signal.
Crash: dictation hotkey listener calls TSMGetInputSourceProperty off main thread (EXC_BREAKPOINT in
voice-hotkeythread)Summary
OpenHuman 0.54.0 crashes on macOS 26.4.1 (Apple Silicon) within ~90 seconds of launch whenever
[dictation] enabled = true. The crash fires on any key press once the dictation hotkey listener is armed. Root cause isrdev::macos::keyboard::Keyboard::string_from_codecallingTSMGetInputSourcePropertyfrom a background thread; macOS 26 enforces that this API runs on the main dispatch queue and aborts the process viadispatch_assert_queue_failwhen it does not.Environment
[dictation] enabled = true,hotkey = "Fn",activation_mode = "push"stt_provider = "whisper",whisper_in_process = true,stt_model_id = "ggml-small.en-q5_1.bin"(locally downloaded)[voice_server] auto_start = false(dormant; not involved)enabled = false(also confirmed not involved)Reproduction
~/.openhuman/users/<id>/config.toml: set[dictation] enabled = trueSIGTRAP(EXC_BREAKPOINT)100% reproducible on the test system. Setting
[dictation] enabled = falseeliminates the crash.Crash signature
Exception:
EXC_BREAKPOINT (SIGTRAP), codes0x1, 0x18c7d64fc(thebrk 1inside_dispatch_assert_queue_fail).Termination reason:
Namespace SIGNAL, Code 5, Trace/BPT trap: 5.Root cause
rdev(Narsil/rdev) registers a Quartz event tap and invokes a Rust callback (raw_callback) on the SkyLight event-dispatch thread. Inside that callback,Keyboard::string_from_codecallsTISCopyCurrentKeyboardInputSource/TSMGetInputSourcePropertyto resolve the keycode to a printable string. As of recent macOS releases (definitely 26.x), the TextInputSources API containsdispatch_assert_queue(dispatch_get_main_queue())and the process is killed when invoked off the main thread.This is a well-known rdev limitation; see for example rdev issues tagged "macOS" / "thread" upstream.
Suggested fixes (in order of preference)
tauri-plugin-global-shortcutorglobal-hotkeyfor the dictation listener. Both crates dispatch the hotkey callback on the Tauri/main thread and avoid the off-main TSM call entirely.kTISNotifySelectedKeyboardInputSourceChanged(delivered on the main run loop).dispatch_sync(dispatch_get_main_queue(), ...)inside rdev (or in OpenHuman's rdev integration shim). This is the smallest patch but adds latency to every key event.Option 1 is the most robust and removes a dependency that has caused multiple cross-platform headaches historically.
Workaround
Set
[dictation] enabled = falseinconfig.toml. The app launches and runs normally; only the global dictation hotkey is lost. In-app voice surfaces appear unaffected.Attached artifacts
.ipscrash report (from~/Library/Logs/DiagnosticReports/OpenHuman-2026-05-26-023634.ips) — available on request, redacted of system identifiers.config.tomlshowing the triggering setting — available on request.Why this matters
[dictation] enabled = trueis the documented path for the headline voice feature. Until this is fixed, every macOS 26 user who enables dictation hits a crash loop within seconds of launch, with no in-app indication of cause (logs show clean dictation startup, then process exit). The user-facing impact is "app quits unexpectedly" with no actionable signal.