# Troubleshooting Common failure modes and their fixes. ## "Device not found" ``` Error: Device 'VirtualMicSink.monitor' not found. Available sources: [...] To create VirtualMicSink, run: scripts/audio-sink.sh start ``` **Fix:** ```bash uv run stt-daemon setup ``` Then route audio into VirtualMicSink and try again. (`scripts/audio-sink.sh start` is a thin wrapper over the same subcommand.) ## "Can't select or copy text in the TUI" **Cause:** Textual enables terminal mouse reporting, so mouse events are consumed by the app instead of the terminal, and native drag-selection never starts. **Fix:** hold `Shift` while dragging to force your terminal's native selection, then copy as usual. (Alternatively, launching the app with mouse support disabled — `app.run(mouse=False)` — restores plain selection but loses in-app scrolling/clicks.) ## High latency / slow transcription By default, the daemon uses `compute_type = "int8"` and `device = "cpu"`, which trades speed for accuracy. To speed up: 1. **Pin `cpu_threads` to the physical core count**: `cpu_threads = 16` in `[whisper]` (CTranslate2's automatic choice is often slower than an explicit count on many-core CPUs) 2. **Reduce model size**: `--model base` or `--model tiny` 3. **Enable GPU** (if you have CUDA): `--device cuda` with `compute_type float16` 4. **Reduce beam_size**: Edit config to `beam_size = 1` (already default, very fast) Example: ```bash uv run stt-daemon --model base --device cuda ``` ## "Transcription backlog reached N of M" This warning means the single transcription worker is falling behind: utterances are arriving faster than they are being transcribed, and once the queue is full the oldest ones get dropped. The worker is serial by design (faster-whisper's model isn't thread-safe), so fix it by making transcription faster rather than by queueing more: 1. **Smaller/faster model**: `--model base` or `tiny` 2. **GPU**: `--device cuda` with `compute_type float16` 3. **Raise `backlog_warn_at`** or `queue_size` in `[daemon]` only to make the warning/dropping less likely — it hides the symptom, not the cause. ## Spanish / English / Other languages The Faster-Whisper model is **multilingual by default**. It auto-detects language or can be forced: ```bash uv run stt-daemon --language es # Force Spanish uv run stt-daemon --language en # Force English uv run stt-daemon --language auto # Auto-detect (default) ``` The output includes the detected language tag: `[14:23:45] (es) ...`. ## Recognizing Spanish vs. English better - **Force the language**: Use `--language es` or `--language en` - **Use a larger model**: `--model medium` or `--model large` (slower, more accurate) - **Tune VAD aggressiveness**: Lower values (0–1) capture more, higher values (2–3) are stricter ## Audio device not responding / parec hangs If `parec` seems stuck: 1. Check if the source exists: `pactl list short sources` 2. Kill the daemon (Ctrl+C) 3. Verify PulseAudio is running: `systemctl status pulseaudio` or check journalctl 4. Restart PulseAudio if needed: `systemctl restart pulseaudio` 5. Re-run the daemon ## Sounddevice backend issues If using `--backend sounddevice` and getting import errors: ```bash uv sync --extra portaudio ``` Then verify available devices: ```bash python -c "import sounddevice as sd; print(sd.query_devices())" ``` Use the device index or name as `--device 0` or `--device "Microphone (...)`. ## No transcriptions appearing 1. Check VAD is firing: `uv run stt-daemon -v` (verbose logging) 2. Verify audio is loud enough: `uv run stt-daemon --check` (check RMS) 3. Reduce `aggressiveness` (0–1 instead of 2–3) to detect quieter speech 4. Check `min_utterance_ms` isn't too high (default 300ms should be fine) ## Whisper model not loading ``` Error loading model: ... ``` The model is downloaded on first run (~1.5 GB for `small`). Check: 1. Disk space: `df -h` (needs ~2 GB free) 2. Internet: Model is downloaded from Hugging Face 3. HF cache location: Models go to `~/.cache/huggingface/hub/` by default You can override the cache: ```bash export HF_HOME=/path/to/cache uv run stt-daemon --model small ``` ## Frozen with no transcripts (silent freeze) With `[debug] enabled = true`, the debug log is the first place to look — it is the only structured record (child stdout/stderr is discarded, so there is no raw stderr complement). Each line is one JSON object in `troubleshoot/debug/YYYY-MM-DD.jsonl`; no transcript text is stored. ```bash tail -n 100 troubleshoot/debug/$(date +%F).jsonl ``` Read the tail in this order: 1. **`device_ok`**: `false` means the capture device went away — check `pactl list short sources`, restart PulseAudio, and re-run (see "Audio device not responding" above). 2. **`worker_stall_s`**: compare against `[assistant] worker_stall_ms` (default 15000). A rising stall with a growing `queue_depth` means transcription is falling behind — use a smaller model or GPU (see "Transcription backlog" above). A `worker-stall` signal line marks the threshold crossing. 3. **Capture `reopen` lines**: repeated reopen attempt/fail lines mean the source keeps dropping; fix the device rather than raising queue sizes. 4. **Supervisor exit lines**: a child early-exit / control-socket timeout / first-child-exit code line means the supervisor already reaped a child — restart via `uv run stt-daemon up --with-tui` and check which child exited first. 5. **Assistant `queue-full` / `eviction-drop` / `llm-timeout` / `triage-brief-failure`**: backpressure or LLM-side stalls on the assistant lane, not a capture problem. 6. **Triage lifecycle `triage-submit` / `triage-skip` / `triage-result` / `triage-brief`**: why triage did or did not run. `triage-submit.trigger` is `settled` or `periodic` (a long monologue fires `periodic`), `triage-skip.reason` is `empty_state` / `lane_refused`, and `triage-brief.action` is `dispatch`, `refuse` (`stale` / `outstanding`), or `skip` (`gate` / `extract_disabled` / `no_brief_fn`). A run that only ever shows `triage-submit` with no `triage-result` means the Laya predict is still loading or stalled. When the system is healthy again, clear the log manually (`rm -r troubleshoot/`); there is no automatic retention job. Keep `troubleshoot/` on local storage — a slow/NFS/FUSE mount can block the only capturer. ## Speaker identification See [SPEAKERS.md](SPEAKERS.md) for the feature reference. ### `sherpa-onnx` not installed **Cause:** the speaker extra is opt-in and a plain `uv sync` does not install `sherpa-onnx`. **Fix:** ```bash uv sync --extra speaker ``` ### `ImportError: libonnxruntime.so: cannot open shared object file` **Cause:** the native onnxruntime that `sherpa-onnx-core` bundles is missing. This happens when the environment was synced without the extra, or before the core wheel was included. **Fix:** re-sync the speaker extra, which now installs `sherpa-onnx-core`: ```bash uv sync --extra speaker ``` If you also use the triage panel, request both extras so neither stack is dropped: ```bash uv sync --extra speaker --extra triage ``` The daemon itself only warns and keeps running with `speaker_status = "none"`; the hard error appears when you run the one-off `stt-daemon speaker-enroll`. ### Model missing, or `enabled = true` with no `model_path` **Cause:** with `enabled = true` but `model_path` unset, missing, or pointing at a model that fails to load, the feature disables gracefully. It warns once and never crashes the daemon: no lane is built, no store is touched, and every record keeps `speaker_status = "none"`. **Fix:** download the model into the gitignored `models/` directory and set `model_path`: ```bash curl -L -o models/nemo_en_titanet_small.onnx \ "https://github.com/k2-fsa/sherpa-onnx/releases/download/speaker-recongition-models/nemo_en_titanet_small.onnx" # then: [speaker] model_path = "models/nemo_en_titanet_small.onnx" ``` ### Every turn shows `Unknown` **Cause:** either the feature is disabled (check `speaker_status = "none"` in the JSONL records), or the thresholds do not fit your audio. `threshold_id` (0.60) and `threshold_new` (0.45) are model- and channel-dependent starting points; the wrong model for the room, or a bad microphone, can keep every score below `threshold_new`. **Fix:** confirm `enabled = true` and a loadable `model_path` first. Then calibrate on real far-field audio: read `speaker_score` from the JSONL records and lower the thresholds until repeated voices cross them, or switch to a fallback model. See [SPEAKERS.md](SPEAKERS.md) for the bands and the model options. ### Naming reports `unknown_speaker` **Cause:** `stt-ctl speaker-name` failed because the id is not in the assistant's cached roster. The assistant caches speakers from the `speakers` bus events, so an id that has not yet appeared in an event is unknown to it. **Fix:** run `uv run stt-ctl speaker-list` to see the ids the assistant knows, then retry with one of those. If the roster looks stale, let a turn settle so a fresh `speakers` event arrives, or restart the assistant. ### Resetting the store **Cause:** a bad prototype, an unwanted provisional, or a corrupt store. **Fix:** `delete` and `merge` are not supported in v1. Stop the daemon, remove the id from `speakers/speakers.json` and its rows from `speakers/prototypes.npz`, then restart. To start clean, stop the daemon, delete `speakers/`, and restart.