hermes - 💡(How to fix) Fix Discord voice Opus loading fails on NixOS unless libopus is loaded explicitly [1 pull requests]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Discord voice support can fail on NixOS even when libopus is installed, because discord.py relies on automatic Opus discovery via ctypes.util.find_library("opus"). In a Nix/NixOS runtime, that lookup may return None, since shared libraries live in Nix store paths and are not necessarily discoverable through the usual linker cache.

As a result, Hermes can have the Discord voice dependencies installed and still fail when joining or using a voice channel, unless Opus is explicitly loaded by path before voice connection/playback.

Error Message

Inside the Hermes Python runtime:

Root Cause

Discord voice support can fail on NixOS even when libopus is installed, because discord.py relies on automatic Opus discovery via ctypes.util.find_library("opus"). In a Nix/NixOS runtime, that lookup may return None, since shared libraries live in Nix store paths and are not necessarily discoverable through the usual linker cache.

Fix Action

Fixed

Code Example

import ctypes.util
import discord

print(ctypes.util.find_library("opus"))
# None

---

import discord

discord.opus.load_opus("opus")
# OSError: opus: cannot open shared object file: No such file or directory

---

import discord

discord.opus.load_opus("libopus.so")
print(discord.opus.is_loaded())
# True

---

import discord

discord.opus.load_opus("/path/to/libopus.so")
print(discord.opus.is_loaded())
# True

---

def load_opus(name: str) -> None:
    """Loads the libopus shared library for use with voice.

    If this function is not called then the library uses the function
    ctypes.util.find_library and then loads that one if available.

    Not loading a library and attempting to use PCM based AudioSources will
    lead to voice not working.
    """
    global _lib
    _lib = libopus_loader(name)

---

On Linux the full extension is required to load the library,
e.g. libopus.so.1.

---

import os
import logging

import discord

logger = logging.getLogger(__name__)


def ensure_discord_opus_loaded() -> None:
    """Load libopus for discord.py voice support if it is not already loaded.

    This is especially important on NixOS, where ctypes.util.find_library("opus")
    may return None even when libopus is installed.
    """
    if discord.opus.is_loaded():
        return

    candidates = [
        os.getenv("DISCORD_OPUS_LIBRARY"),
        "libopus.so",
        "libopus.so.0",
    ]

    errors: list[str] = []

    for candidate in candidates:
        if not candidate:
            continue

        try:
            discord.opus.load_opus(candidate)
            logger.info("Loaded Discord Opus library from %s", candidate)
            return
        except Exception as exc:
            errors.append(f"{candidate}: {type(exc).__name__}: {exc}")

    raise RuntimeError(
        "Discord voice requires libopus, but Hermes could not load it. "
        "Set DISCORD_OPUS_LIBRARY to the full path of libopus.so. "
        f"Tried: {errors}"
    )

---

async def join_voice_channel(...):
    ensure_discord_opus_loaded()
    voice_client = await voice_channel.connect(...)
    ...

---

DISCORD_OPUS_LIBRARY=/path/to/libopus.so

---

DISCORD_OPUS_LIBRARY = "${pkgs.libopus}/lib/libopus.so";

---

import ctypes.util
import discord

print("find_library:", ctypes.util.find_library("opus"))
print("opus loaded before:", discord.opus.is_loaded())

discord.opus.load_opus("libopus.so")

print("opus loaded after:", discord.opus.is_loaded())

---

find_library: None
opus loaded before: False
opus loaded after: True
RAW_BUFFERClick to expand / collapse

Bug: Discord voice Opus loading fails on NixOS unless libopus is loaded explicitly

Summary

Discord voice support can fail on NixOS even when libopus is installed, because discord.py relies on automatic Opus discovery via ctypes.util.find_library("opus"). In a Nix/NixOS runtime, that lookup may return None, since shared libraries live in Nix store paths and are not necessarily discoverable through the usual linker cache.

As a result, Hermes can have the Discord voice dependencies installed and still fail when joining or using a voice channel, unless Opus is explicitly loaded by path before voice connection/playback.

Observed behavior

Inside the Hermes Python runtime:

import ctypes.util
import discord

print(ctypes.util.find_library("opus"))
# None

Attempting to load by the bare library name can fail:

import discord

discord.opus.load_opus("opus")
# OSError: opus: cannot open shared object file: No such file or directory

Loading by the full shared-library filename or absolute path works:

import discord

discord.opus.load_opus("libopus.so")
print(discord.opus.is_loaded())
# True

And with an explicit Nix-provided path:

import discord

discord.opus.load_opus("/path/to/libopus.so")
print(discord.opus.is_loaded())
# True

Expected behavior

Hermes should explicitly load Opus before using Discord voice features when an Opus library path is configured, instead of relying only on discord.py automatic discovery.

This would make Discord voice support work reliably on NixOS and other environments where ctypes.util.find_library("opus") does not find the library.

Relevant upstream behavior

discord.py documents that load_opus() may need to be called manually:

def load_opus(name: str) -> None:
    """Loads the libopus shared library for use with voice.

    If this function is not called then the library uses the function
    ctypes.util.find_library and then loads that one if available.

    Not loading a library and attempting to use PCM based AudioSources will
    lead to voice not working.
    """
    global _lib
    _lib = libopus_loader(name)

On Linux, the docstring also notes that the full extension is required:

On Linux the full extension is required to load the library,
e.g. libopus.so.1.

Proposed fix

Before joining a Discord voice channel or playing audio through Discord voice, Hermes should ensure Opus is loaded.

A small helper could live near the Discord gateway adapter / voice code:

import os
import logging

import discord

logger = logging.getLogger(__name__)


def ensure_discord_opus_loaded() -> None:
    """Load libopus for discord.py voice support if it is not already loaded.

    This is especially important on NixOS, where ctypes.util.find_library("opus")
    may return None even when libopus is installed.
    """
    if discord.opus.is_loaded():
        return

    candidates = [
        os.getenv("DISCORD_OPUS_LIBRARY"),
        "libopus.so",
        "libopus.so.0",
    ]

    errors: list[str] = []

    for candidate in candidates:
        if not candidate:
            continue

        try:
            discord.opus.load_opus(candidate)
            logger.info("Loaded Discord Opus library from %s", candidate)
            return
        except Exception as exc:
            errors.append(f"{candidate}: {type(exc).__name__}: {exc}")

    raise RuntimeError(
        "Discord voice requires libopus, but Hermes could not load it. "
        "Set DISCORD_OPUS_LIBRARY to the full path of libopus.so. "
        f"Tried: {errors}"
    )

Then call it before Discord voice connection/playback, for example:

async def join_voice_channel(...):
    ensure_discord_opus_loaded()
    voice_client = await voice_channel.connect(...)
    ...

or immediately before any code path that creates/uses a Discord voice client.

Suggested configuration support

Hermes should support an optional environment variable:

DISCORD_OPUS_LIBRARY=/path/to/libopus.so

For Nix/NixOS packaging, this can be set to the package-provided library path, for example conceptually:

DISCORD_OPUS_LIBRARY = "${pkgs.libopus}/lib/libopus.so";

This avoids depending on LD_LIBRARY_PATH or linker-cache behavior.

Why this matters on NixOS

On NixOS, installing libopus does not guarantee that ctypes.util.find_library("opus") can discover it. The library exists, but discovery can still fail unless the exact path is provided or the runtime linker environment is arranged specifically.

So the issue is not that Opus is missing. The issue is that Discord voice code relies on automatic Opus discovery, which is unreliable under Nix-style library isolation.

Minimal reproduction

import ctypes.util
import discord

print("find_library:", ctypes.util.find_library("opus"))
print("opus loaded before:", discord.opus.is_loaded())

discord.opus.load_opus("libopus.so")

print("opus loaded after:", discord.opus.is_loaded())

On affected NixOS setups:

find_library: None
opus loaded before: False
opus loaded after: True

Proposed outcome

Hermes should explicitly load Opus from DISCORD_OPUS_LIBRARY when available, with sensible fallback candidates, before using Discord voice. If loading fails, the error should clearly tell the user to set DISCORD_OPUS_LIBRARY to the full libopus.so path.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Hermes should explicitly load Opus before using Discord voice features when an Opus library path is configured, instead of relying only on discord.py automatic discovery.

This would make Discord voice support work reliably on NixOS and other environments where ctypes.util.find_library("opus") does not find the library.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix Discord voice Opus loading fails on NixOS unless libopus is loaded explicitly [1 pull requests]