# NousResearch/hermes-agent · Discord bot @everyone ping prevention fix

## Open now

- Nothing open on GitHub.

A bug was identified where the Discord bot could ping @everyone and roles because allowed_mentions was never configured on the client. Multiple fix attempts address this by defaulting allowed_mentions to block such pings.

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #11479. Verify it addresses your case.
- **Recommended:** read PR #11479 (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: `resolved` · deciding PR(s): `pr-11479`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: 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

- #11339: Discord bot can ping @everyone — \`allowed_mentions\` never set on the client

## Prior & competing PRs

- **#11479: fix(discord): default allowed_mentions to block @everyone and role pings**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `gateway/config.py`, `gateway/platforms/discord.py`, `scripts/release.py`, `tests/gateway/test_discord_allowed_mentions.py`, `tests/gateway/test_discord_connect.py`, `website/docs/reference/environment-variables.md`, `website/docs/user-guide/messaging/discord.md`
  - fix hunks:
  - `gateway/config.py`

    ```diff
    @@ -617,6 +617,20 @@ def load_gateway_config() -> GatewayConfig:
                         if isinstance(ntc, list):
                             ntc = ",".join(str(v) for v in ntc)
                         os.environ["DISCORD_NO_THREAD_CHANNELS"] = str(ntc)
    +                # allow_mentions: granular control over what the bot can ping.
    +                # Safe defaults (no @everyone/roles) are applied in the adapter;
    +                # these YAML keys only override when set and let users opt back
    +                # into unsafe modes (e.g. roles=true) if they actually want it.
    +                allow_mentions_cfg = discord_cfg.get("allow_mentions")
    +                if isinstance(allow_mentions_cfg, dict):
    +                    for yaml_key, env_key in (
    +                        ("everyone", "DISCORD_ALLOW_MENTION_EVERYONE"),
    +                        ("roles", "DISCORD_ALLOW_MENTION_ROLES"),
    +                        ("users", "DISCORD_ALLOW_MENTION_USERS"),
    +                        ("replied_user", "DISCORD_ALLOW_MENTION_REPLIED_USER"),
    +                    ):
    +                        if yaml_key in allow_mentions_cfg and not os.getenv(env_key):
    +                            os.environ[env_key] = str(allow_mentions_cfg[yaml_key]).lower()
     
                 # Telegram settings → env vars (env vars take precedence)
                 telegram_cfg = yaml_cfg.get("telegram", {})
    ```
  - `gateway/platforms/discord.py`

    ```diff
    @@ -80,6 +80,41 @@ def check_discord_requirements() -> bool:
         return DISCORD_AVAILABLE
     
     
    +def _build_allowed_mentions():
    +    """Build Discord ``AllowedMentions`` with safe defaults, overridable via env.
    +
    +    Discord bots default to parsing ``@everyone``, ``@here``, role pings, and
    +    user pings when ``allowed_mentions`` is unset on the client — any LLM
    +    output or echoed user content that contains ``@everyone`` would therefore
    +    ping the whole server. We explicitly deny ``@everyone`` and role pings
    +    by default and keep user / replied-user pings enabled so normal
    +    conversation still works.
    +
    +    Override via environment variables (or ``discord.allow_mentions.*`` in
    +    config.yaml):
    +
    +        DISCORD_ALLOW_MENTION_EVERYONE      default false  — @everyone + @here
    +        DISCORD_ALLOW_MENTION_ROLES         default false  — @role pings
    +        DISCORD_ALLOW_MENTION_USERS         default true   — @user pings
    +        DISCORD_ALLOW_MENTION_REPLIED_USER  default true   — reply-ping author
    +    """
    +    if not DISCORD_AVAILABLE:
    +        return None
    +
    +    def _b(name: str, default: bool) -> bool:
    +        raw = os.getenv(name, "").strip().lower()
    +        if not raw:
    +            return default
    +        return raw in ("true", "1", "yes", "on")
    +
    +    return discord.AllowedMentions(
    +        everyone=_b("DISCORD_ALLOW_MENTION_EVERYONE", False),
    +        roles=_b("DISCORD_ALLOW_MENTION_ROLES", False),
    +        users=_b("DISCORD_ALLOW_MENTION_USERS", True),
    +        replied_user=_b("DISCORD_ALLOW_MENTION_REPLIED_USER", True),
    +    )
    +
    +
     class VoiceReceiver:
         """Captures and decodes voice audio from a Discord voice channel.
     
    ```

    ```diff
    @@ -556,10 +591,15 @@ async def connect(self) -> bool:
                 if proxy_url:
                     logger.info("[%s] Using proxy for Discord: %s", self.name, proxy_url)
     
    -            # Create bot — proxy= for HTTP, connector= for SOCKS
    +            # Create bot — proxy= for HTTP, connector= for SOCKS.
    +            # allowed_mentions is set with safe defaults (no @everyone/roles)
    +            # so LLM output or echoed user content can't ping the whole
    +            # server; override per DISCORD_ALLOW_MENTION_* env vars or the
    +            # discord.allow_mentions.* block in config.yaml.
                 self._client = commands.Bot(
                     command_prefix="!",  # Not really used, we handle raw messages
                     intents=intents,
    +                allowed_mentions=_build_allowed_mentions(),
                     **proxy_kwargs_for_bot(proxy_url),
                 )
                 adapter_self = self  # capture for closure
    ```
  - `scripts/release.py`

    ```diff
    @@ -230,6 +230,7 @@
         "[redacted:email]": "ZaynJarvis",
         "[redacted:email]": "ZaynJarvis",
         "[redacted:email]": "malaiwah",
    +    "[redacted:email]": "malaiwah",
         "[redacted:email]": "v1k22",
     }
     
    ```
  - `tests/gateway/test_discord_allowed_mentions.py`

    ```diff
    @@ -0,0 +1,155 @@
    +"""Tests for the Discord ``allowed_mentions`` safe-default helper.
    +
    +Ensures the bot defaults to blocking ``@everyone`` / ``@here`` / role pings
    +so an LLM response (or echoed user content) can't spam a whole server —
    +and that the four ``DISCORD_ALLOW_MENTION_*`` env vars correctly opt back
    +in when an operator explicitly wants a different policy.
    +"""
    +
    +import sys
    +from types import SimpleNamespace
    +from unittest.mock import MagicMock
    +
    +import pytest
    +
    +
    +class _FakeAllowedMentions:
    +    """Stand-in for ``discord.AllowedMentions`` that exposes the same four
    +    boolean flags as real attributes so the test can assert on them.
    +    """
    +
    +    def __init__(self, *, everyone=True, roles=True, users=True, replied_user=True):
    +        self.everyone = everyone
    +        self.roles = roles
    +        self.users = users
    +        self.replied_user = replied_user
    +
    +    def __repr__(self) -> str:  # pragma: no cover - debug helper
    +        return (
    +            f"AllowedMentions(everyone={self.everyone}, roles={self.roles}, "
    +            f"users={self.users}, replied_user={self.replied_user})"
    +        )
    +
    +
    +def _ensure_discord_mock():
    +    """Install (or augment) a mock ``discord`` module.
    +
    +    Other test modules in this directory stub ``discord`` via
    +    ``sys.modules.setdefault`` — whichever test file imports first wins and
    +    our full module is then silently dropped. We therefore ALWAYS force
    +    ``AllowedMentions`` onto whatever is currently in ``sys.modules["discord"]``;
    +    that's the only attribute this test file actually needs real behavior from.
    +    """
    +    if "discord" in sys.modules and hasattr(sys.modules["discord"], "__file__"):
    +        sys.modules["discord"].AllowedMentions = _FakeAllowedMentions
    +        return
    +
    +    if sys.modules.get("discord") is None:
    +        discord_mod = MagicMock()
    +        discord_mod.Intents.default.return_value = MagicMock()
    +        discord_mod.Client = MagicMock
    +        discord_mod.File = MagicMock
    +        discord_mod.DMChannel = type("DMChannel", (), {})
    +        discord_mod.Thread = type("Thread", (), {})
    +        discord_mod.ForumChannel = type("ForumChannel", (), {})
    +        discord_mod.ui = SimpleNamespace(View=object, button=lambda *a, **k: (lambda fn: fn), Button=object)
    +        discord_mod.ButtonStyle = SimpleNamespace(success=1, primary=2, danger=3, green=1, blurple=2, red=3, grey=4, secondary=5)
    +        discord_mod.Color = SimpleNamespace(orange=lambda: 1, green=lambda: 2, blue=lambda: 3, red=lambda: 4)
    +        discord_mod.Interaction = object
    +        discord_mod.Embed = MagicMock
    +        discord_mod.app_commands = SimpleNamespace(
    +            describe=lambda **kwargs: (lambda fn: fn),
    +            choices=lambda **kwargs: (lambda fn: fn),
    +            Choice=lambda **kwargs: SimpleNamespace(**kwargs),
    +        )
    +        discord_mod.opus = SimpleNamespace(is_loaded=lambda: True)
    +
    +        ext_mod = MagicMock()
    +        commands_mod = MagicMock()
    +        commands_mod.Bot = MagicMock
    +        ext_mod.commands = commands_mod
    +
    +        sys.modules["discord"] = discord_mod
    +        sys.modules.setdefault("discord.ext", ext_mod)
    +        sys.modules.setdefault("discord.ext.commands", commands_mod)
    +
    +    # Whether we just installed the mock OR the mock was already installed
    +    # by another test's _ensure_discord_mock, force the AllowedMentions
    +    # stand-in onto it — _build_allowed_mentions() reads this attribute.
    +    sys.modules["discord"].AllowedMentions = _FakeAllowedMentions
    +
    +
    +_ensure_discord_mock()
    +
    +from gateway.platforms.discord import _build_allowed_mentions  # noqa: E402
    +
    +
    +# The four DISCORD_ALLOW_MENTION_* env vars that _build_allowed_mentions reads.
    +# Cleared before each test so env leakage from other tests never masks a regression.
    +_ENV_VARS = (
    +    "DISCORD_ALLOW_MENTION_EVERYONE",
    +    "DISCORD_ALLOW_MENTION_ROLES",
    +    "DISCORD_ALLOW_MENTION_USERS",
    +    "DISCORD_ALLOW_MENTION_REPLIED_USER",
    +)
    +
    +
    [redacted:email](autouse=True)
    +def _clear_allowed_mention_env(monkeypatch):
    +    for name in _ENV_VARS:
    +        monkeypatch.delenv(name, raising=False)
    +
    +
    +def test_safe_defaults_block_everyone_and_roles():
    +    am = _build_allowed_mentions()
    +    assert am.everyone is False, "default must NOT allow @everyone/@here pings"
    +    assert am.roles is False, "default must NOT allow role pings"
    +    assert am.users is True, "default must allow user pings so replies work"
    +    assert am.replied_user is True, "default must allow reply-reference pings"
    +
    +
    +def test_env_var_opts_back_into_everyone(monkeypatch):
    +    monkeypatch.setenv("DISCORD_ALLOW_MENTION_EVERYONE", "true")
    +    am = _build_allowed_mentions()
    +    assert am.everyone is True
    +    # other defaults unaffected
    +    assert am.roles is False
    +    assert am.users is True
    +    assert am.replied_user is True
    +
    +
    +def test_env_var_can_disable_users(monkeypatch):
    +    monkeypatch.setenv("DISCORD_ALLOW_MENTION_USERS", "false")
    +    am = _build_allowed_mentions()
    +    assert am.users is False
    +    # safe defaults elsewhere remain
    +    assert am.everyone is False
    +    assert am.roles is False
    +    assert am.replied_user is True
    +
    +
    [redacted:email]("raw, expected", [
    +    ("true", True), ("True", True), ("TRUE", True),
    +    ("1", True), ("yes", True), ("YES", True), ("on", True),
    +    ("false", False), ("False", False), ("0", False),
    +    ("no", False), ("off", False),
    +    ("", False),                 # empty falls back to default (False for everyone)
    +    ("garbage", False),          # unknown falls back to default
    +    (" true ", True),            # whitespace tolerated
    +])
    +def test_everyone_boolean_parsing(monkeypatch, raw, expected):
    +    monkeypatch.setenv("DISCORD_ALLOW_MENTION_EVERYONE", raw)
    +    am = _build_allowed_mentions()
    +    assert am.everyone is expected
    +
    +
    +def test_all_four_knobs_together(monkeypatch):
    +    monkeypatch.setenv("DISCORD_ALLOW_MENTION_EVERYONE", "true")
    +    monkeypatch.setenv("DISCORD_ALLOW_MENTION_ROLES", "true")
    +    monkeypatch.setenv("DISCORD_ALLOW_MENTION_USERS", "false")
    +    monkeypatch.setenv("DISCORD_ALLOW_MENTION_REPLIED_USER", "false")
    +    am = _build_allowed_mentions()
    +    assert am.everyone is True
    +    assert am.roles is True
    +    assert am.users is False
    +    assert am.replied_user is False
    ```
  - `tests/gateway/test_discord_connect.py`

    ```diff
    @@ -8,37 +8,60 @@
     from gateway.config import PlatformConfig
     
     
    +class _FakeAllowedMentions:
    +    """Stand-in for ``discord.AllowedMentions`` — exposes the same four
    +    boolean flags as real attributes so tests can assert on safe defaults.
    +    """
    +
    +    def __init__(self, *, everyone=True, roles=True, users=True, replied_user=True):
    +        self.everyone = everyone
    +        self.roles = roles
    +        self.users = users
    +        self.replied_user = replied_user
    +
    +
     def _ensure_discord_mock():
    +    """Install (or augment) a mock ``discord`` module.
    +
    +    Always force ``AllowedMentions`` onto whatever is in ``sys.modules`` —
    +    other test files also stub the module via ``setdefault``, and we need
    +    ``_build_allowed_mentions()``'s return value to have real attribute
    +    access regardless of which file loaded first.
    +    """
         if "discord" in sys.modules and hasattr(sys.modules["discord"], "__file__"):
    +        sys.modules["discord"].AllowedMentions = _FakeAllowedMentions
             return
     
    -    discord_mod = MagicMock()
    -    discord_mod.Intents.default.return_value = MagicMock()
    -    discord_mod.Client = MagicMock
    -    discord_mod.File = MagicMock
    -    discord_mod.DMChannel = type("DMChannel", (), {})
    -    discord_mod.Thread = type("Thread", (), {})
    -    discord_mod.ForumChannel = type("ForumChannel", (), {})
    -    discord_mod.ui = SimpleNamespace(View=object, button=lambda *a, **k: (lambda fn: fn), Button=object)
    -    discord_mod.ButtonStyle = SimpleNamespace(success=1, primary=2, danger=3, green=1, blurple=2, red=3, grey=4, secondary=5)
    -    discord_mod.Color = SimpleNamespace(orange=lambda: 1, green=lambda: 2, blue=lambda: 3, red=lambda: 4)
    -    discord_mod.Interaction = object
    -    discord_mod.Embed = MagicMock
    -    discord_mod.app_commands = SimpleNamespace(
    -        describe=lambda **kwargs: (lambda fn: fn),
    -        choices=lambda **kwargs: (lambda fn: fn),
    -        Choice=lambda **kwargs: SimpleNamespace(**kwargs),
    -    )
    -    discord_mod.opus = SimpleNamespace(is_loaded=lambda: True)
    -
    -    ext_mod = MagicMock()
    -    commands_mod = MagicMock()
    -    commands_mod.Bot = MagicMock
    -    ext_mod.commands = commands_mod
    -
    -    sys.modules.setdefault("discord", discord_mod)
    -    sys.modules.setdefault("discord.ext", ext_mod)
    -    sys.modules.setdefault("discord.ext.commands", commands_mod)
    +    if sys.modules.get("discord") is None:
    +        discord_mod = MagicMock()
    +        discord_mod.Intents.default.return_value = MagicMock()
    +        discord_mod.Client = MagicMock
    +        discord_mod.File = MagicMock
    +        discord_mod.DMChannel = type("DMChannel", (), {})
    +        discord_mod.Thread = type("Thread", (), {})
    +        discord_mod.ForumChannel = type("ForumChannel", (), {})
    +        discord_mod.ui = SimpleNamespace(View=object, button=lambda *a, **k: (lambda fn: fn), Button=object)
    +        discord_mod.ButtonStyle = SimpleNamespace(success=1, primary=2, danger=3, green=1, blurple=2, red=3, grey=4, secondary=5)
    +        discord_mod.Color = SimpleNamespace(orange=lambda: 1, green=lambda: 2, blue=lambda: 3, red=lambda: 4)
    +        discord_mod.Interaction = object
    +        discord_mod.Embed = MagicMock
    +        discord_mod.app_commands = SimpleNamespace(
    +            describe=lambda **kwargs: (lambda fn: fn),
    +            choices=lambda **kwargs: (lambda fn: fn),
    +            Choice=lambda **kwargs: SimpleNamespace(**kwargs),
    +        )
    +        discord_mod.opus = SimpleNamespace(is_loaded=lambda: True)
    +
    +        ext_mod = MagicMock()
    +        commands_mod = MagicMock()
    +        commands_mod.Bot = MagicMock
    +        ext_mod.commands = commands_mod
    +
    +        sys.modules["discord"] = discord_mod
    +        sys.modules.setdefault("discord.ext", ext_mod)
    +        sys.modules.setdefault("discord.ext.commands", commands_mod)
    +
    +    sys.modules["discord"].AllowedMentions = _FakeAllowedMentions
     
     
     _ensure_discord_mock()
    ```

    ```diff
    @@ -56,8 +79,9 @@ def command(self, *args, **kwargs):
     
     
     class FakeBot:
    -    def __init__(self, *, intents, proxy=None):
    +    def __init__(self, *, intents, proxy=None, allowed_mentions=None, **_):
             self.intents = intents
    +        self.allowed_mentions = allowed_mentions
             self.user = SimpleNamespace(id=999, name="Hermes")
             self._events = {}
             self.tree = FakeTree()
    ```

    ```diff
    @@ -115,8 +139,8 @@ async def test_connect_only_requests_members_intent_when_needed(monkeypatch, all
     
         created = {}
     
    -    def fake_bot_factory(*, command_prefix, intents, proxy=None):
    -        created["bot"] = FakeBot(intents=intents)
    +    def fake_bot_factory(*, command_prefix, intents, proxy=None, allowed_mentions=None, **_):
    +        created["bot"] = FakeBot(intents=intents, allowed_mentions=allowed_mentions)
             return created["bot"]
     
         monkeypatch.setattr(discord_platform.commands, "Bot", fake_bot_factory)
    ```

    ```diff
    @@ -126,6 +150,13 @@ def fake_bot_factory(*, command_prefix, intents, proxy=None):
     
         assert ok is True
         assert created["bot"].intents.members is expected_members_intent
    +    # Safe-default AllowedMentions must be applied on every connect so the
    +    # bot cannot @everyone from LLM output.  Granular overrides live in the
    +    # dedicated test_discord_allowed_mentions.py module.
    +    am = created["bot"].allowed_mentions
    +    assert am is not None, "connect() must pass an AllowedMentions to commands.Bot"
    +    assert am.everyone is False
    +    assert am.roles is False
     
         await adapter.disconnect()
     
    ```

    ```diff
    @@ -144,7 +175,11 @@ async def test_connect_releases_token_lock_on_timeout(monkeypatch):
         monkeypatch.setattr(
             discord_platform.commands,
             "Bot",
    -        lambda **kwargs: FakeBot(intents=kwargs["intents"], proxy=kwargs.get("proxy")),
    +        lambda **kwargs: FakeBot(
    +            intents=kwargs["intents"],
    +            proxy=kwargs.get("proxy"),
    +            allowed_mentions=kwargs.get("allowed_mentions"),
    +        ),
         )
     
         async def fake_wait_for(awaitable, timeout):
    ```

    ```diff
    @@ -172,7 +207,7 @@ async def test_connect_does_not_wait_for_slash_sync(monkeypatch):
     
         created = {}
     
    -    def fake_bot_factory(*, command_prefix, intents, proxy=None):
    +    def fake_bot_factory(*, command_prefix, intents, proxy=None, allowed_mentions=None, **_):
             bot = SlowSyncBot(intents=intents, proxy=proxy)
             created["bot"] = bot
             return bot
    ```
  - `website/docs/reference/environment-variables.md`

    ```diff
    @@ -196,6 +196,10 @@ For cloud sandbox backends, persistence is filesystem-oriented. `TERMINAL_LIFETI
     | `DISCORD_IGNORED_CHANNELS` | Comma-separated channel IDs where the bot never responds |
     | `DISCORD_NO_THREAD_CHANNELS` | Comma-separated channel IDs where bot responds without auto-threading |
     | `DISCORD_REPLY_TO_MODE` | Reply-reference behavior: `off`, `first` (default), or `all` |
    +| `DISCORD_ALLOW_MENTION_EVERYONE` | Allow the bot to ping `@everyone`/`@here` (default: `false`). See [Mention Control](../user-guide/messaging/discord.md#mention-control). |
    +| `DISCORD_ALLOW_MENTION_ROLES` | Allow the bot to ping `@role` mentions (default: `false`). |
    +| `DISCORD_ALLOW_MENTION_USERS` | Allow the bot to ping individual `@user` mentions (default: `true`). |
    +| `DISCORD_ALLOW_MENTION_REPLIED_USER` | Ping the author when replying to their message (default: `true`). |
     | `SLACK_BOT_TOKEN` | Slack bot token (`xoxb-...`) |
     | `SLACK_APP_TOKEN` | Slack app-level token (`xapp-...`, required for Socket Mode) |
     | `SLACK_ALLOWED_USERS` | Comma-separated Slack user IDs |
    ```
  - `website/docs/user-guide/messaging/discord.md`

    ```diff
    @@ -283,6 +283,10 @@ Discord behavior is controlled through two files: **`~/.hermes/.env`** for crede
     | `DISCORD_IGNORED_CHANNELS` | No | — | Comma-separated channel IDs where the bot **never** responds, even when `@mentioned`. Takes priority over all other channel settings. |
     | `DISCORD_NO_THREAD_CHANNELS` | No | — | Comma-separated channel IDs where the bot responds directly in the channel instead of creating a thread. Only relevant when `DISCORD_AUTO_THREAD` is `true`. |
     | `DISCORD_REPLY_TO_MODE` | No | `"first"` | Controls reply-reference behavior: `"off"` — never reply to the original message, `"first"` — reply-reference on the first message chunk only (default), `"all"` — reply-reference on every chunk. |
    +| `DISCORD_ALLOW_MENTION_EVERYONE` | No | `false` | When `false` (default), the bot cannot ping `@everyone` or `@here` even if its response contains those tokens. Set to `true` to opt back in. See [Mention Control](#mention-control) below. |
    +| `DISCORD_ALLOW_MENTION_ROLES` | No | `false` | When `false` (default), the bot cannot ping `@role` mentions. Set to `true` to allow. |
    +| `DISCORD_ALLOW_MENTION_USERS` | No | `true` | When `true` (default), the bot can ping individual users by ID. |
    +| `DISCORD_ALLOW_MENTION_REPLIED_USER` | No | `true` | When `true` (default), replying to a message pings the original author. |
     
     ### Config File (`config.yaml`)
     
    ```

    ```diff
    @@ -298,6 +302,11 @@ discord:
       ignored_channels: []            # Channel IDs where bot never responds
       no_thread_channels: []          # Channel IDs where bot responds without threading
       channel_prompts: {}             # Per-channel ephemeral system prompts
    +  allow_mentions:                 # What the bot is allowed to ping (safe defaults)
    +    everyone: false               # @everyone / @here pings (default: false)
    +    roles: false                  # @role pings (default: false)
    +    users: true                   # @user pings (default: true)
    +    replied_user: true            # reply-reference pings the author (default: true)
     
     # Session isolation (applies to all gateway platforms, not just Discord)
     group_sessions_per_user: true     # Isolate sessions per user in shared channels
    ```

    ```diff
    @@ -552,6 +561,34 @@ If you intentionally want a shared room conversation, leave it off — just expe
     Always set `DISCORD_ALLOWED_USERS` to restrict who can interact with the bot. Without it, the gateway denies all users by default as a safety measure. Only add User IDs of people you trust — authorized users have full access to the agent's capabilities, including tool use and system access.
     :::
     
    +### Mention Control
    +
    +By default, Hermes blocks the bot from pinging `@everyone`, `@here`, and role mentions, even if its reply contains those tokens. This prevents a poorly-worded prompt or echoed user content from spamming a whole server. Individual `@user` pings and reply-reference pings (the little "replying to…" chip) stay enabled so normal conversation still works.
    +
    +You can relax these defaults via either env vars or `config.yaml`:
    +
    +```yaml
    +# ~/.hermes/config.yaml
    +discord:
    +  allow_mentions:
    +    everyone: false      # allow the bot to ping @everyone / @here
    +    roles: false         # allow the bot to ping @role mentions
    +    users: true          # allow the bot to ping individual @users
    +    replied_user: true   # ping the author when replying to their message
    +```
    +
    +```bash
    +# ~/.hermes/.env — env vars win over config.yaml
    +DISCORD_ALLOW_MENTION_EVERYONE=false
    +DISCORD_ALLOW_MENTION_ROLES=false
    +DISCORD_ALLOW_MENTION_USERS=true
    +DISCORD_ALLOW_MENTION_REPLIED_USER=true
    +```
    +
    +:::tip
    +Leave `everyone` and `roles` at `false` unless you know exactly why you need them. It is very easy for an LLM to produce the string `@everyone` inside a normal-looking response; without this protection, that would notify every member of your server.
    +:::
    +
     For more information on securing your Hermes Agent deployment, see the [Security Guide](../security.md).
     
     
    ```
- **#11341: fix(discord): default allowed_mentions to block @everyone and role pings**. closed without merge
  - maintainer direction: category `silent_close`; implication `active`
- **#11353: fix(discord): default allowed_mentions to block @everyone and role pings**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#11479`

## Fix status

- Merged to main in PR #11479 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-04-17`. This confirmation is old and may be out of date (a revert/reopen would not have invalidated it here); re-verify.
