Skip to content

fix(voice): skip sounddevice on macOS to avoid TCC media-library prompt - #62601

Closed
simonmmafs wants to merge 2 commits into
NousResearch:mainfrom
simonmmafs:fix/macos-voice-tcc-prompt
Closed

fix(voice): skip sounddevice on macOS to avoid TCC media-library prompt#62601
simonmmafs wants to merge 2 commits into
NousResearch:mainfrom
simonmmafs:fix/macos-voice-tcc-prompt

Conversation

@simonmmafs

Copy link
Copy Markdown
Contributor

Problem

On macOS, play_audio_file() initializes PortAudio/CoreAudio through sounddevice for WAV playback. That init triggers a kTCCServiceMediaLibrary permission dialog even though voice mode never needs media-library access — a confusing prompt for users, and a blocker for headless/gateway installs where nobody is at the screen to click it.

Fix

Skip the sounddevice path entirely on Darwin and fall through to afplay, which handles WAV (and every other format) natively without touching the TCC-gated media stack. Other platforms keep the existing sounddevice-first behavior.

Running as a local patch on a macOS gateway install (v0.15.1 → v0.18.2) since June with no regressions.

🤖 Generated with Claude Code

On macOS, initializing PortAudio/CoreAudio via sounddevice triggers a
kTCCServiceMediaLibrary permission dialog even when no media-library
access is needed. afplay already handles WAV (and every other format)
natively, so route macOS playback straight to it and keep sounddevice
for other platforms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Automated review by Hermes Agent: No obvious issues found.


Reviewed by Hermes Agent

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Automated review by Hermes Agent: No obvious issues found.


Reviewed by Hermes Agent

@alt-glitch alt-glitch added type/bug Something isn't working tool/tts Text-to-speech and transcription P3 Low — cosmetic, nice to have duplicate This issue or pull request already exists labels Jul 11, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #13291 — both route macOS audio playback in play_audio_file() away from the sounddevice/PortAudio path to afplay at the same # Try sounddevice for WAV files branch. #13291 is the earlier, broader version (also skips cue beeps and adds the full afplay subprocess path this PR relies on falling through to). Different framing (TCC media-library prompt here vs CoreAudio crash there), same mechanism at the same site. Marking as duplicate of the earliest canonical PR; maintainer to pick.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating the macOS WAV fallback path. The condition at tools/voice_mode.py:1065 would correctly make that particular path fall through to the existing afplay player.

Problems

  • tools/voice_mode.py:291-321 still imports and plays through sounddevice for cue beeps. Those beeps are invoked by the CLI voice wrapper before recording at hermes_cli/voice.py:427 and after it at hermes_cli/voice.py:536, so this does not yet skip macOS sounddevice output entirely.
  • Streaming ElevenLabs TTS bypasses play_audio_file() and opens sounddevice.OutputStream directly at tools/tts_tool.py:2645-2654.
  • tests/tools/test_voice_mode.py:884-904 covers the generic WAV sounddevice path, but no test covers the new Darwin routing behavior.

Suggested changes

  • Apply one explicit macOS output policy to WAV playback, cue beeps, and streaming TTS, then add Darwin-focused tests proving the selected routes do not initialize sounddevice.
  • Please reconcile this with the overlapping broader approach in #13291 before salvage.

Automated hermes-sweeper review.

Comment thread tools/voice_mode.py Outdated
# On macOS, skip sounddevice entirely — PortAudio/CoreAudio init triggers
# a kTCCServiceMediaLibrary permission prompt even though we don't need it.
# afplay handles all formats natively without touching the media stack.
if file_path.endswith(".wav") and platform.system() != "Darwin":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bypasses sounddevice only for WAV file playback. play_beep() still imports and plays through sounddevice (tools/voice_mode.py:291-321), and the CLI invokes it before recording at hermes_cli/voice.py:427; please cover that output path as well if the goal is to avoid macOS PortAudio/CoreAudio initialization.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 11, 2026
Addresses review on NousResearch#62601. Applies a single rule — no sounddevice for
audio OUTPUT on macOS (PortAudio/CoreAudio init triggers a
kTCCServiceMediaLibrary prompt) — consistently at all three output sites:

- play_audio_file: WAV playback (already routed to afplay) now uses the
  shared _sounddevice_output_allowed() helper.
- play_beep: synthesize the tone with numpy only, then on macOS play it
  via a temp WAV through afplay instead of sounddevice.
- stream_tts_to_speaker (tts_tool): on macOS, skip the sounddevice
  OutputStream so playback falls through to the existing tempfile/afplay path.

Audio INPUT (recording) is untouched — it legitimately needs mic permission.

Tests: TestMacOSAudioOutputPolicy (voice_mode) proves WAV + beep routing
does not import sounddevice on Darwin and still uses it off Darwin;
test_tts_macos_output proves streaming TTS skips the OutputStream on Darwin.
Existing test_play_wav_via_sounddevice pinned to non-Darwin for determinism.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@simonmmafs

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review — addressed all three points in c7571ae by applying one consistent policy ("no sounddevice for audio output on macOS") at every output site rather than just the WAV branch:

  • Cue beeps (play_beep): tone is now synthesized with numpy only, then on macOS played via a temp WAV through afplay (no sounddevice import). Off macOS, unchanged.
  • Streaming ElevenLabs TTS (tts_tool.stream_tts_to_speaker): on macOS the sounddevice.OutputStream is skipped, so playback falls through to the existing _play_via_tempfileplay_audio_fileafplay path.
  • play_audio_file: WAV branch now uses the shared _sounddevice_output_allowed() helper.

Audio input (recording) is deliberately untouched — it legitimately needs mic permission.

Tests: TestMacOSAudioOutputPolicy proves WAV + beep output doesn't import sounddevice on Darwin (and still does off Darwin); test_tts_macos_output proves streaming TTS skips the OutputStream on Darwin. Also pinned the pre-existing test_play_wav_via_sounddevice to non-Darwin so it's deterministic across host OSes.

Happy to reconcile with #13291 — if you'd prefer that one as the canonical fix I can close this, or rebase this consolidated approach on top. Your call.

teknium1 pushed a commit that referenced this pull request Jul 28, 2026
Addresses review on #62601. Applies a single rule — no sounddevice for
audio OUTPUT on macOS (PortAudio/CoreAudio init triggers a
kTCCServiceMediaLibrary prompt) — consistently at all three output sites:

- play_audio_file: WAV playback (already routed to afplay) now uses the
  shared _sounddevice_output_allowed() helper.
- play_beep: synthesize the tone with numpy only, then on macOS play it
  via a temp WAV through afplay instead of sounddevice.
- stream_tts_to_speaker (tts_tool): on macOS, skip the sounddevice
  OutputStream so playback falls through to the existing tempfile/afplay path.

Audio INPUT (recording) is untouched — it legitimately needs mic permission.

Tests: TestMacOSAudioOutputPolicy (voice_mode) proves WAV + beep routing
does not import sounddevice on Darwin and still uses it off Darwin;
test_tts_macos_output proves streaming TTS skips the OutputStream on Darwin.
Existing test_play_wav_via_sounddevice pinned to non-Darwin for determinism.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@teknium1

Copy link
Copy Markdown
Contributor

Merged into main via consolidated salvage PR #73520 (merge e04c2a9ebd). Your macOS no-sounddevice-for-output policy (WAV, beeps, streaming TTS — avoiding the kTCCServiceMediaLibrary prompt; 2 commits) was cherry-picked with your authorship, with earliest-crash-work credit to @ChuanQiao1128's #13291.

Your contribution is credited to you in git history. Thank you! Closing this PR as merged-via-salvage.

@teknium1 teknium1 closed this Jul 29, 2026
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
Addresses review on NousResearch#62601. Applies a single rule — no sounddevice for
audio OUTPUT on macOS (PortAudio/CoreAudio init triggers a
kTCCServiceMediaLibrary prompt) — consistently at all three output sites:

- play_audio_file: WAV playback (already routed to afplay) now uses the
  shared _sounddevice_output_allowed() helper.
- play_beep: synthesize the tone with numpy only, then on macOS play it
  via a temp WAV through afplay instead of sounddevice.
- stream_tts_to_speaker (tts_tool): on macOS, skip the sounddevice
  OutputStream so playback falls through to the existing tempfile/afplay path.

Audio INPUT (recording) is untouched — it legitimately needs mic permission.

Tests: TestMacOSAudioOutputPolicy (voice_mode) proves WAV + beep routing
does not import sounddevice on Darwin and still uses it off Darwin;
test_tts_macos_output proves streaming TTS skips the OutputStream on Darwin.
Existing test_play_wav_via_sounddevice pinned to non-Darwin for determinism.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/tts Text-to-speech and transcription type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants