hermes - ✅(Solved) Fix Bug: Wildcard config values (allowed_channels: "*") silently drop all Discord messages [2 pull requests, 1 participants]

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…
GitHub stats
NousResearch/hermes-agent#14920Fetched 2026-04-24 10:44:18
View on GitHub
Comments
0
Participants
1
Timeline
11
Reactions
0
Participants
Timeline (top)
labeled ×5referenced ×3cross-referenced ×2closed ×1

When allowed_channels: "*" or free_response_channels: "*" is set in config.yaml under the discord: section, the gateway silently drops ALL messages from server channels. No error or warning is logged — the bot simply ignores every message.

Error Message

When allowed_channels: "*" or free_response_channels: "*" is set in config.yaml under the discord: section, the gateway silently drops ALL messages from server channels. No error or warning is logged — the bot simply ignores every message.

  • No error or warning produced — messages just vanish
  • If "*" is unsupported, raise a clear error at config load

Root Cause

Root Cause

Fix Action

Workaround

Remove allowed_channels from config entirely (no restriction = all channels allowed) or list specific channel IDs.

PR fix notes

PR #14930: Fix/discord allowed channels wildcard

Description (problem / solution / changelog)

fix(discord): honor wildcard '*' in DISCORD_ALLOWED_CHANNELS

allowed_channels: "*" in config.yaml (or DISCORD_ALLOWED_CHANNELS="*" as an env var) is intended to allow the bot to respond in all channels, but it was silently dropping every message instead.

Root cause: The filter splits the raw value by comma into a set — so "*" becomes {"*"}. It then checks channel_ids & allowed_channels, where channel_ids contains numeric Discord IDs like {"1234567890"}. The intersection with {"*"} is always empty, so every message was silently rejected.

Every other platform (Signal, Slack, Telegram) explicitly handles "*" as a wildcard short-circuit before the set intersection. Discord was the only one missing it.

Summary

Add a "*" not in allowed_channels guard before the set-intersection check in on_message, consistent with all other platform allowlist implementations.

Type of Change

  • Bug fix
  • New feature
  • Performance improvement
  • Documentation/Tests

Objective

N/A (bug fix)

Testing

  • Unit tests added/updated — new tests/gateway/test_discord_allowed_channels.py covers wildcard, wildcard-in-list, exact match, non-match, empty list, and whitespace-only entries (6 tests, all passing)
  • Integration tests added/updated
  • All existing tests pass

Breaking Changes

  • This PR contains breaking changes

Checklist

  • Code follows project style guidelines (make lint passes)
  • Self-review completed
  • Documentation updated where necessary
  • No secrets or sensitive information committed

Related Issues

Closes #14920

Changed files

  • agent/context_compressor.py (modified, +10/-3)
  • gateway/platforms/discord.py (modified, +1/-1)
  • tests/agent/test_context_compressor.py (modified, +20/-0)
  • tests/gateway/test_discord_allowed_channels.py (added, +48/-0)

PR #15052: fix: P1 batch — Discord wildcard, ACP+MCP, NO_PROXY bypass, resume-after-compression

Description (problem / solution / changelog)

Four high-impact P1 fixes, salvaged with contributor attribution preserved via rebase-merge.

Fixes

  • #14920 — Discord wildcard "*" silently drops all messages Salvaged from @mrunmayee17's PR #14930 (allowed_channels). Extended to cover free_response_channels and ignored_channels which had the same "*" literal-in-set bug, and added regression tests for all three.
  • #14986 — MCP tools invisible in ACP sessions (hardcoded toolsets) Salvaged from @camaragon's PR #14709 unchanged. ACP sessions now expand enabled_toolsets with configured MCP servers at agent creation, and the tool-surface refresh after dynamic MCP server registration preserves the additions.
  • #14966 — _build_keepalive_http_client ignores NO_PROXY → 502 on local endpoints Salvaged from @shamork's PR #14546 unchanged. Added regression tests for _get_proxy_for_base_url() and the full keepalive-client path.
  • #15000 — --resume <id> loads empty chat after context compression New fix (no contributor PR existed). SessionDB.resolve_resume_session_id() walks the parent_session_id chain forward and redirects resume targets to the first descendant with messages. Wired into all three CLI resume entry points (_preload_resumed_session, _init_agent, /resume).

Also closed

  • #14933, #14938 — DeepSeek V4 reasoning_content issues closed as unactionable (both submitted with empty issue bodies; asked reporters to re-file with a reproducer).
  • PRs #14930, #14709, #14546 will be closed with credit referencing this PR after merge.
  • PR #14885 (duplicate NO_PROXY fix by @fqx) will be closed with credit — @shamork's implementation was preferred because it reuses stdlib urllib.request.proxy_bypass_environment() which correctly handles wildcards, leading dots, and CIDR-like patterns that #14885's custom matcher missed.

Attribution

FixContributorEmail → GH
#14920@mrunmayee17[email protected]
#14986@camaragonnoreply form
#14966@shamork[email protected]

All three added to scripts/release.py AUTHOR_MAP in the tail commit.

Validation

Test fileResult
tests/gateway/test_discord_allowed_channels.py15/15 ✓
tests/acp/test_session.py + tests/acp/test_server.py83/83 ✓
tests/run_agent/test_create_openai_client_proxy_env.py9/9 ✓ (4 new NO_PROXY tests)
tests/hermes_state/test_resolve_resume_session_id.py8/8 ✓ (new file)

E2E for #15000: reproduced the exact 6-session compression chain from the issue body (5 empty + 1 with 119 messages); resolve_resume_session_id correctly redirects to the 5th session from any of the 5 empty ones and returns the msg-bearing session unchanged.

Merge method

Rebase merge — each contributor's cherry-picked commit preserves their authorship. Squash would flatten all seven commits into one authored by us, losing credit.

Closes #14920, #14966, #14986, #15000 Supersedes #14546, #14709, #14885, #14930

Changed files

  • acp_adapter/server.py (modified, +9/-3)
  • acp_adapter/session.py (modified, +28/-1)
  • cli.py (modified, +49/-0)
  • gateway/platforms/discord.py (modified, +13/-4)
  • hermes_state.py (modified, +65/-0)
  • run_agent.py (modified, +26/-5)
  • scripts/release.py (modified, +4/-0)
  • tests/acp/test_server.py (modified, +7/-1)
  • tests/acp/test_session.py (modified, +37/-0)
  • tests/gateway/test_discord_allowed_channels.py (added, +104/-0)
  • tests/hermes_state/test_resolve_resume_session_id.py (added, +96/-0)
  • tests/run_agent/test_create_openai_client_proxy_env.py (modified, +76/-1)

Code Example

if ac is not None and not os.getenv("DISCORD_ALLOWED_CHANNELS"):
    os.environ["DISCORD_ALLOWED_CHANNELS"] = str(ac)  # sets it to literal "*"

---

allowed_channels_raw = os.getenv("DISCORD_ALLOWED_CHANNELS", "")
if allowed_channels_raw:
    allowed_channels = {ch.strip() for ch in allowed_channels_raw.split(",") if ch.strip()}
    # allowed_channels = {"*"}
    if not (channel_ids and allowed_channels):
        # channel_ids = {"1490953023155998720"} — never matches {"*"}
        logger.debug("[%s] Ignoring message in non-allowed channel: %s", self.name, channel_ids)
        return  # silently drops ALL messages

---

non-allowed-channel DROP: ch={"1490953023155998720"} allowed={"*"}

---

allowed_channels_raw = os.getenv("DISCORD_ALLOWED_CHANNELS", "")
if allowed_channels_raw and allowed_channels_raw.strip() != "*":
    allowed_channels = {ch.strip() for ch in allowed_channels_raw.split(",") if ch.strip()}
    if not (channel_ids and allowed_channels):
        logger.debug("[%s] Ignoring message in non-allowed channel: %s", self.name, channel_ids)
        return

---

if isinstance(raw, str) and raw.strip() == "*":
    return {"*"}  # Caller checks for "*" membership separately

---

is_free_channel = ("*" in free_channels) or bool(channel_ids and free_channels) or is_voice_linked_channel
RAW_BUFFERClick to expand / collapse

Description

When allowed_channels: "*" or free_response_channels: "*" is set in config.yaml under the discord: section, the gateway silently drops ALL messages from server channels. No error or warning is logged — the bot simply ignores every message.

Environment

  • Platform: Discord gateway
  • Config: discord.require_mention: false, discord.allowed_channels: "*", discord.free_response_channels: "*"
  • Log level: INFO (default)

Root Cause

1. gateway/config.py (line ~644-647)

The config loader converts the YAML value to an environment variable without special handling for wildcards:

if ac is not None and not os.getenv("DISCORD_ALLOWED_CHANNELS"):
    os.environ["DISCORD_ALLOWED_CHANNELS"] = str(ac)  # sets it to literal "*"

2. gateway/platforms/discord.py_handle_message() (line ~3233)

The allowed channels check splits "*" into a set containing the literal string "*":

allowed_channels_raw = os.getenv("DISCORD_ALLOWED_CHANNELS", "")
if allowed_channels_raw:
    allowed_channels = {ch.strip() for ch in allowed_channels_raw.split(",") if ch.strip()}
    # allowed_channels = {"*"}
    if not (channel_ids and allowed_channels):
        # channel_ids = {"1490953023155998720"} — never matches {"*"}
        logger.debug("[%s] Ignoring message in non-allowed channel: %s", self.name, channel_ids)
        return  # silently drops ALL messages

The same issue affects free_response_channels: "*"_discord_free_response_channels() returns {"*"} and is_free_channel always evaluates to False.

Why this is hard to diagnose

  • The drop is logged via logger.debug() — invisible at default INFO log level
  • Gateway connects successfully, appears online, receives MESSAGE_CREATE events
  • No error or warning produced — messages just vanish
  • Users naturally expect "*" to mean "all channels"

Investigation Trail

  1. Bot online but unresponsive — gateway connected, websocket ESTAB to Discord
  2. Standalone test bot worked — confirmed Discord delivers events; issue is in Hermes code
  3. Added logger.info() traces (since logger.debug() is silent at INFO level)
  4. Discovered messages pass all initial filters — on_message fires, dedup passes, user allowlist passes, reaches _handle_message
  5. Found the silent drop — _handle_message returned instantly. Traces revealed:
non-allowed-channel DROP: ch={"1490953023155998720"} allowed={"*"}

Channel ID never matches the literal string "*".

Suggested Fix

Three small changes in gateway/platforms/discord.py:

Fix 1: Handle wildcard in allowed_channels check

allowed_channels_raw = os.getenv("DISCORD_ALLOWED_CHANNELS", "")
if allowed_channels_raw and allowed_channels_raw.strip() != "*":
    allowed_channels = {ch.strip() for ch in allowed_channels_raw.split(",") if ch.strip()}
    if not (channel_ids and allowed_channels):
        logger.debug("[%s] Ignoring message in non-allowed channel: %s", self.name, channel_ids)
        return

Fix 2: Handle wildcard in _discord_free_response_channels()

if isinstance(raw, str) and raw.strip() == "*":
    return {"*"}  # Caller checks for "*" membership separately

Fix 3: Handle wildcard in is_free_channel check

is_free_channel = ("*" in free_channels) or bool(channel_ids and free_channels) or is_voice_linked_channel

Alternative: Config-time validation

  • If "*" is unsupported, raise a clear error at config load
  • If supported, document it and handle it properly
  • At minimum, log channel filter drops at WARNING level, not DEBUG

Workaround

Remove allowed_channels from config entirely (no restriction = all channels allowed) or list specific channel IDs.

Impact

Silent data loss — bot appears healthy but ignores all server messages. Affects users who naturally use "*" as a wildcard, especially since YAML format encourages it.

extent analysis

TL;DR

To fix the issue where the Discord bot silently drops all messages from server channels when allowed_channels or free_response_channels is set to "*" in config.yaml, apply the suggested fixes to handle wildcards in the allowed channels check.

Guidance

  • Apply the three suggested fixes in gateway/platforms/discord.py to handle wildcards in allowed_channels and free_response_channels checks.
  • Consider implementing config-time validation to raise an error or log a warning when "*" is used as a wildcard.
  • As a temporary workaround, remove allowed_channels from the config or list specific channel IDs.

Example

The suggested fixes include modifying the allowed_channels check to handle the wildcard "*":

allowed_channels_raw = os.getenv("DISCORD_ALLOWED_CHANNELS", "")
if allowed_channels_raw and allowed_channels_raw.strip() != "*":
    allowed_channels = {ch.strip() for ch in allowed_channels_raw.split(",") if ch.strip()}
    if not (channel_ids and allowed_channels):
        logger.debug("[%s] Ignoring message in non-allowed channel: %s", self.name, channel_ids)
        return

Notes

The issue is caused by the literal string "*" being used in the allowed_channels and free_response_channels checks, which does not match the expected channel IDs. The suggested fixes aim to handle this wildcard correctly.

Recommendation

Apply the suggested fixes to handle wildcards in the allowed channels check, as this will correctly handle the "*" wildcard and prevent silent data loss.

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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING