# NousResearch/hermes-agent · Voice audio detection in WSL, SSH, Docker environments

## Open now

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

## TL;DR

- 5 member(s): 0 open issue(s), 3 closed issue(s), 0 open PR(s), 2 merged PR(s), 0 PR(s) closed without merging.
- A fix was merged to main in PR #22534. Verify it addresses your case.
- **Recommended:** read PR #22534 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-22534,pr-35719`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member 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.

## Closed / duplicate issues

- #21203: \[Bug\]: no audio device error in hermes chat
- #32009: \[Setup\]: Audio in a docker container: Suggested solution with instructions
- #35622: \[Feature\]: Allow tts over ssh if pulseadio is reacheable

## Prior & competing PRs

- **#22534: fix(voice): honor PULSE_SERVER/PIPEWIRE_REMOTE inside Docker (#21203)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `tests/tools/test_voice_mode.py`, `tools/voice_mode.py`
  - fix hunks:
  - `tests/tools/test_voice_mode.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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 · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `tests/tools/test_voice_mode.py`, `tools/voice_mode.py`

## Fix status

- Merged to main in PR #22534 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-31`.
- Subsystem: `environments`
