cjpais/Handy · critical
failed to register transcription signal handlers
Error message
failed to register transcription signal handlers
What it means
Panic on macOS when signal-hook's Signals::new cannot install handlers for SIGUSR1/SIGUSR2. Registration allocates the signal set and installs dispositions; it fails when the process cannot manage signals — another component already owning these user signals incompatibly, sigaction/kqueue registration errors (e.g. EMFILE), or restricted runtimes (sandboxes, some test harnesses) that block signal management.
Solutions
- Identify the other owner of SIGUSR1/SIGUSR2 in the process and let exactly one component register them
- Degrade gracefully instead of expecting: log a warning and continue — remote control still works via handy --toggle-transcription through the single-instance channel
- If a sandbox policy blocks signal management, allow user signals or run without the signal feature
Example fix
// before
#[cfg(target_os = "macos")]
let mut signals =
Signals::new([SIGUSR1, SIGUSR2]).expect("failed to register transcription signal handlers");
// after — signals are an optional remote control; skip them on failure
#[cfg(target_os = "macos")]
let mut signals = match Signals::new([SIGUSR1, SIGUSR2]) {
Ok(s) => s,
Err(e) => {
log::warn!("signal handlers disabled: {e}");
return;
}
}; Defensive patterns
Strategy: fallback
Prevention
- Register signal handlers exactly once at process start and make registration non-fatal (log and continue)
- When embedding Handy's core in another app or test harness, let the host own user signals and rely on the CLI remote control instead
- Remember signals are an optional remote control; handy --toggle-transcription via single-instance always works without them
When it happens
Trigger: setup_signal_handler at startup on macOS: Signals::new([SIGUSR1, SIGUSR2]) returns Err because a prior in-process handler claims one of the signals incompatibly, or registration fails under sandbox/resource constraints.
Common situations: Embedding Handy's core in another app or test runner that also installs SIGUSR handlers; running under debuggers/profilers that intercept user signals; forks that re-register handlers or call setup twice.
Related errors
- failed to create secure input warning item
- Canary transcription failed
- Cohere transcription failed
- Complete model directory not found
- Complete model file not found in HF cache or models dir
AI-assisted analysis of cjpais/Handy@c6fa60da2f (2026-08-17).
Data as JSON: /api/errors/1f66a202d7117123.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/signal_handle.rs:38
c.send_external_input(binding_id, source);
} else {
warn!("TranscriptionCoordinator not initialized");
}
}
/// Listen for Unix signals that remotely toggle transcription.
///
/// SIGUSR2 toggles plain transcription on all Unix platforms. SIGUSR1
/// (transcription with post-processing) is only handled on macOS: on Linux,
/// WebKitGTK's JavaScriptCore garbage collector sends SIGUSR1 to its own
/// threads to suspend them, so handling it caused phantom recordings on every
/// GC cycle (#1660). Linux users should use `handy --toggle-post-process`
/// instead.
#[cfg(unix)]
pub fn setup_signal_handler(app_handle: AppHandle) {
#[cfg(target_os = "macos")]
let mut signals =
Signals::new([SIGUSR1, SIGUSR2]).expect("failed to register transcription signal handlers");
#[cfg(not(target_os = "macos"))]
let mut signals =
Signals::new([SIGUSR2]).expect("failed to register transcription signal handlers");
#[cfg(target_os = "macos")]
debug!("Signal handlers registered (SIGUSR1, SIGUSR2)");
#[cfg(not(target_os = "macos"))]
debug!("Signal handler registered (SIGUSR2; SIGUSR1 is left to WebKitGTK)");
thread::spawn(move || {
for sig in signals.forever() {
let (binding_id, signal_name) = match sig {
#[cfg(target_os = "macos")]
SIGUSR1 => ("transcribe_with_post_process", "SIGUSR1"),
SIGUSR2 => ("transcribe", "SIGUSR2"),
_ => continue,
};
debug!("Received {signal_name}");
send_transcription_input(&app_handle, binding_id, signal_name);
}View on GitHub (pinned to c6fa60da2f)