Voice audio detection in WSL, SSH, Docker environments

NousResearch/hermes-agent · cluster 21203 · environments

Data as of

Nothing open on GitHub. Fix merged in #22534. Check it covers your case.

The short answer

0open issues
0open PRs
5items grouped
Known?
Yes. 5 related items grouped here, 3 issues already closed.
Fix event
A fix was merged to main in PR #22534. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PRs #22534, #35719. That is separate from the fix event.
Still open
Nothing open on GitHub.

Fixes and feature requests focus on making audio device detection honor environment variables like PULSE_SERVER and PIPEWIRE_REMOTE so voice features work in WSL, SSH sessions, and Docker containers.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #22534 fix(voice): honor PULSE_SERVER/PIPEWIRE_REMOTE inside Docker (#21203)

    merged to main

    files: tests/tools/test_voice_mode.py, tools/voice_mode.py

    Show the diff
    • tests/tools/test_voice_mode.py
      @@ -216,6 +216,60 @@ def test_termux_api_package_without_android_app_blocks_voice(self, monkeypatch):
               assert any("Termux:API Android app is not installed" in w for w in result["warnings"])
       
       
      +    def test_docker_with_pulse_server_allows_voice(self, monkeypatch):
      +        """Docker with PULSE_SERVER set should NOT block voice mode (#21203)."""
      +        monkeypatch.delenv("SSH_CLIENT", raising=False)
      +        monkeypatch.delenv("SSH_TTY", raising=False)
      +        monkeypatch.delenv("SSH_CONNECTION", raising=False)
      +        monkeypatch.setenv("PULSE_SERVER", "unix:/run/user/1000/pulse/native")
      +        monkeypatch.delenv("PIPEWIRE_REMOTE", raising=False)
      +        monkeypatch.setattr("hermes_constants.is_container", lambda: True)
      +        monkeypatch.setattr("tools.voice_mode._import_audio",
      +                            lambda: (MagicMock(), MagicMock()))
      +
      +        from tools.voice_mode import detect_audio_environment
      +        result = detect_audio_environment()
      +
      +        assert result["available"] is True
      +        assert result["warnings"] == []
      +        assert any("container" in n.lower() for n in result.get("notices", []))
      +
      +    def test_docker_with_pipewire_remote_allows_voice(self, monkeypatch):
      +        """Docker with PIPEWIRE_REMOTE set should NOT block voice mode (#21203)."""
      +        monkeypatch.delenv("SSH_CLIENT", raising=False)
      +        monkeypatch.delenv("SSH_TTY", raising=False)
      +        monkeypatch.delenv("SSH_CONNECTION", raising=False)
      +        monkeypatch.delenv("PULSE_SERVER", raising=False)
      +        monkeypatch.setenv("PIPEWIRE_REMOTE", "/run/user/1000/pipewire-0")
      +        monkeypatch.setattr("hermes_constants.is_container", lambda: True)
      +        monkeypatch.setattr("tools.voice_mode._import_audio",
      +                            lambda: (MagicMock(), MagicMock()))
      +
      +        from tools.voice_mode import detect_audio_environment
      +        result = detect_audio_environment()
      +
      +        assert result["available"] is True
      +        assert result["warnings"] == []
      +        assert any("container" in n.lower() for n in result.get("notices", []))
      +
      +    def test_docker_without_audio_forwarding_blocks_voice(self, monkeypatch):
      +        """Docker without PULSE_SERVER/PIPEWIRE_REMOTE keeps blocking voice mode."""
      +        monkeypatch.delenv("SSH_CLIENT", raising=False)
      +        monkeypatch.delenv("SSH_TTY", raising=False)
      +        monkeypatch.delenv("SSH_CONNECTION", raising=False)
      +        monkeypatch.delenv("PULSE_SERVER", raising=False)
      +        monkeypatch.delenv("PIPEWIRE_REMOTE", raising=False)
      +        monkeypatch.setattr("hermes_constants.is_container", lambda: True)
      +        monkeypatch.setattr("tools.voice_mode._import_audio",
      +                            lambda: (MagicMock(), MagicMock()))
      +
      +        from tools.voice_mode import detect_audio_environment
      +        result = detect_audio_environment()
      +
      +        assert result["available"] is False
      +        assert any("container" in w.lower() for w in result["warnings"])
      +        assert any("PULSE_SERVER" in w or "PIPEWIRE_REMOTE" in w for w in result["warnings"])
      +
           def test_termux_api_microphone_allows_voice_without_sounddevice(self, monkeypatch):
               monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
               monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr")
    • tools/voice_mode.py
      @@ -102,10 +102,23 @@ def detect_audio_environment() -> dict:
           if any(os.environ.get(v) for v in ('SSH_CLIENT', 'SSH_TTY', 'SSH_CONNECTION')):
               warnings.append("Running over SSH -- no audio devices available")
       
      -    # Docker/Podman container detection
      +    # Docker/Podman container detection — honor host audio forwarding.
      +    # When the user mounts a PulseAudio/PipeWire socket into the container
      +    # and points PULSE_SERVER / PIPEWIRE_REMOTE at it, audio works fine
      +    # (issue #21203).  Only block when no forwarding is configured.
           from hermes_constants import is_container
           if is_container():
      -        warnings.append("Running inside Docker container -- no audio devices")
      +        if os.environ.get('PULSE_SERVER') or os.environ.get('PIPEWIRE_REMOTE'):
      +            notices.append("Running inside container (Docker/Podman/LXC) with host audio forwarding")
      +        else:
      +            warnings.append(
      +                "Running inside container (Docker/Podman/LXC) -- no audio devices.\n"
      +                "  Forward host audio with one of (substitute $XDG_RUNTIME_DIR for your runtime dir,\n"
      +                "  typically /run/user/$UID):\n"
      +                "    PulseAudio:  -v $XDG_RUNTIME_DIR/pulse/native:$XDG_RUNTIME_DIR/pulse/native \\\n"
      +                "                 -e PULSE_SERVER=unix:$XDG_RUNTIME_DIR/pulse/native\n"
      +                "    PipeWire:    -e PIPEWIRE_REMOTE=$XDG_RUNTIME_DIR/pipewire-0"
      +            )
       
           # WSL detection — PulseAudio bridge makes audio work in WSL.
           # Only block if PULSE_SERVER is not configured.
  • #35719 fix(voice): allow /voice over SSH when a sound server is reachable

    merged to main

    files: tests/tools/test_voice_mode.py, tools/voice_mode.py

Closed issues · 3
  • Closed#21203[Bug]: no audio device error in hermes chat
  • Closed#32009[Setup]: Audio in a docker container: Suggested solution with instructions
  • Closed#35622[Feature]: Allow tts over ssh if pulseadio is reacheable
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.31: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
environments
Merged PRs
#22534, #35719
Closed issues
3
Attempts
0 PRs closed without merging
Fix link checked
31 May 2026

For your agent

The same facts, typed.