Skip to content

First press-and-hold after idle records nothing: transient mic reopen failure suspends the in-flight hotkey gesture #130

Closed

Description

@0x4nur4g

HEX version

2.1.25 (signed DMG, CFBundleVersion 20125)

Platform

macOS

System details

macOS 26.6.2 (build 25G83), MacBook Pro Mac15,6 (Apple M3 Pro, 18 GB)

What happened?

With the dictation hotkey bound to a modifier-only chord (right shift) and Microphone mode set to "Release when idle", the first press-and-hold after the app has been idle records nothing. The indicator flashes, the error tone plays, and no text is pasted. Releasing and pressing again records normally.

Expected: the hold records, or at worst the first press recovers within itself rather than being thrown away.

Steps to reproduce

  1. Settings: dictation hotkey = right shift (modifier-only), double-tap lock enabled, Microphone mode = "Release when idle".
  2. Leave Hex idle for a few minutes. All observed failures required the capture device to have been closed for >=146 s.
  3. Press and hold the hotkey and speak.
  4. Observed: the capture aborts ~150 ms in, error tone, no output.
  5. Release and press again: records normally.

Evidence

Redacted excerpts from process.log:

WARN voice_control::audio: selected microphone is unavailable; using automatic selection
INFO voice_control::audio: dictation microphone reopened device=<redacted>
ERROR voice_control::audio: microphone stream failed error=A buffer underrun or overrun occurred.
WARN voice_control::audio: microphone stream stopped; reopening it

The matching entries in live.ndjson:

17:01:23.810  dictation started
17:01:23.964  dictation {"failed": "Microphone capture was interrupted; reconnecting."}

Aggregate over roughly five weeks of logs (about 770 captures):

  • 34 microphone stream failed events, 33 of which surfaced as a failed dictation reading Microphone capture was interrupted; reconnecting.
  • Every one of them followed the capture device being closed for >=146 s (min 146.5 s, median about 50 minutes). Zero failures occurred below that.
  • 624 reopens succeeded, 93 of them with under 5 s idle, so reopening is normally fine; the cold open is what is fragile.
  • Each aborted session ends 20-76 ms after its own dictation microphone reopened line, so how long the user held the key has no bearing on when it ends.

Suspected cause

src/recognition.rs handles DictationAudioEvent::Interrupted by calling abandon_capture(), which calls hotkeys.suspend_all(). That discards the gesture the user is still holding, so the subsequent release is a no-op. The recovery machinery already exists (backoff in MicrophoneRecovery in src/audio.rs, pending_capture re-armed on Reopened in src/dictation_audio.rs), but the hotkey gesture does not survive the interruption.

Possible fix: on an interruption during a hotkey capture, keep the gesture alive (do not suspend_all) and re-arm the pending capture when the device reopens, letting the existing 250 ms to 5 s backoff recover inside the same press.

Additional context

Workaround: setting Microphone mode to "Keep ready (fast)" removes the cold open and the symptom stops.

Possibly related: #54.

One separate observation, explicitly unverified. Of presses taken after >=300 s idle, 44% are short (<0.35 s) and are discarded by the 300 ms MINIMUM_HOLD_DURATION, versus 14-20% at short idle. Those session ends do not track the microphone reopen, and their distribution looks identical at short and long idle. I could not separate "the user tapped" from the state machine discarding the hold (the HotkeyAction::Discard arm in src/suppression.rs), because the logs carry no raw input events and there is no tracing::debug! anywhere in the input path. Mentioning it in case it shares a cause with #54.

Activity

  1. kitlangton commented on Oct 18, 2026

    @kitlangton
    Collaborator

    This should be fixed in 2.2.0, which just shipped. When the microphone glitches while reopening after idle, Hex now keeps your held press and reopens the mic instead of discarding the capture.

    I'm closing this for now. If you still see the first hold after idle record nothing on 2.2.0, please reopen with the new process.log excerpt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions