TROUBLESHOOTING.md 8.6 KB

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:

scripts/audio-sink.sh start

Then route audio into VirtualMicSink and try again.

"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:

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:

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:

uv sync --extra portaudio

Then verify available devices:

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:

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 debug/YYYY-MM-DD.jsonl; no transcript text is stored.

tail -n 100 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.

When the system is healthy again, clear the log manually (rm debug/*); there is no automatic retention job. Keep debug/ on local storage — a slow/NFS/FUSE mount can block the only capturer.

Speaker identification

See 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:

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:

uv sync --extra speaker

If you also use the triage panel, request both extras so neither stack is dropped:

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:

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 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.