hermes - ✅(Solved) Fix [Bug]: Discord auto-thread failure silently falls back to inline channel reply [1 pull requests, 1 comments, 2 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#20243Fetched 2026-05-06 06:37:57
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
1
Author
Participants
Timeline (top)
labeled ×4commented ×1cross-referenced ×1

Error Message

2026-05-05 21:25:37,979 WARNING gateway.platforms.discord: [Discord] Auto-thread creation failed. Direct error: Cannot connect to host discord.com:443 ssl:default [None]. Fallback error: Cannot connect to host discord.com:443 ssl:default [None]

Root Cause

This happened in a normal Discord server channel with auto-threading enabled. The user sent a top-level channel message. Hermes attempted to create a thread, Discord thread creation failed because the adapter could not connect to discord.com:443, and Hermes then continued processing the message and replied directly in the parent channel.

Fix Action

Fixed

PR fix notes

PR #20260: fix(discord): auto-thread failure must not silently fall back to inline reply (#20243)

Description (problem / solution / changelog)

Summary

When `discord.auto_thread: true` and Hermes sees a top-level server-channel message that should be routed to a freshly created thread, `_auto_create_thread` was best-effort: a transient `Cannot connect to host discord.com:443` returned `None`, then `_handle_message` evaluated `effective_channel = auto_threaded_channel or message.channel` and let the agent run inline in the parent channel — dumping a new task into a shared channel and breaking thread-first Discord workflows. Reported in #20243.

Fix

Two changes turn this into a fail-closed routing failure:

  1. Retry recoverable thread-create errors. `_auto_create_thread` now retries the primary + seed-message paths once after a 750 ms backoff. Most of the failures the issue describes are transient `aiohttp` connect errors that recover within a couple of seconds.
  2. Fail closed in the call site. `_handle_message` treats `None` from `_auto_create_thread` as a hard failure: send a short visible "could not create a Discord thread" notice in the parent channel so the user knows to retry, then `return` without invoking the agent. The notify-send itself is wrapped so a secondary connect error (the same outage that broke the thread) doesn't turn into an unhandled exception.

Test plan

  • `tests/gateway/test_discord_channel_controls.py` — 16 tests pass (14 existing + 2 new in the auto-thread-failure section)
  • `tests/gateway/test_discord_free_response.py` — 20 tests pass (18 existing, 2 retargeted for the new contract)
  • Adjacent files (slash_commands, bot_filter, bot_auth_bypass, system_messages, imports, connect, race_polish, model_picker) — 72 tests pass

Two pre-existing tests in `test_discord_channel_controls` and `test_discord_free_response` implicitly relied on the silent inline fallback (their `FakeTextChannel` has no `create_thread`, so the auto-thread path always failed, yet they asserted the agent was invoked). They now stub auto-thread creation or set `DISCORD_AUTO_THREAD=false` so each test stays focused on what it was written to verify (ignored-channels, free-response gating, mention strip).

Adds two regression tests covering (a) the failure-with-notification path and (b) the secondary error path where even `message.channel.send` raises — the agent must still not run in either case.

Fixes #20243

Changed files

  • gateway/platforms/discord.py (modified, +61/-21)
  • tests/gateway/test_discord_channel_controls.py (modified, +75/-0)
  • tests/gateway/test_discord_free_response.py (modified, +8/-0)

Code Example

discord:
  require_mention: false
  free_response_channels: ''
  allowed_channels: ''
  auto_thread: true

---

2026-05-05 21:25:37,979 WARNING gateway.platforms.discord: [Discord] Auto-thread creation failed. Direct error: Cannot connect to host discord.com:443 ssl:default [None]. Fallback error: Cannot connect to host discord.com:443 ssl:default [None]

---

Hermes Agent v0.12.0 (2026.4.30)
Project: /home/hansong/.hermes/hermes-agent
Python: 3.11.15
OpenAI SDK: 2.32.0
Current git commit: b816fd4e2

---

2026-05-05 21:25:37,979 WARNING gateway.platforms.discord: [Discord] Auto-thread creation failed. Direct error: Cannot connect to host discord.com:443 ssl:default [None]. Fallback error: Cannot connect to host discord.com:443 ssl:default [None]

---

async def _auto_create_thread(self, message):
    try:
        thread = await message.create_thread(...)
        return thread
    except Exception as direct_error:
        try:
            seed_msg = await message.channel.send(...)
            thread = await seed_msg.create_thread(...)
            return thread
        except Exception as fallback_error:
            logger.warning(...)
            return None

---

thread = await self._auto_create_thread(message)
if thread:
    parent_channel_id = str(message.channel.id)
    is_thread = True
    thread_id = str(thread.id)
    auto_threaded_channel = thread

---

discord:
  auto_thread_failure_mode: error   # error | inline
RAW_BUFFERClick to expand / collapse

Bug Description

When Discord auto-threading is enabled, Hermes silently falls back to answering inline in the parent channel if auto-thread creation fails. For users who rely on Discord threads as the conversation/session boundary, this is worse than a visible failure: a new task unexpectedly lands in the shared channel and breaks the channel organization model.

This happened in a normal Discord server channel with auto-threading enabled. The user sent a top-level channel message. Hermes attempted to create a thread, Discord thread creation failed because the adapter could not connect to discord.com:443, and Hermes then continued processing the message and replied directly in the parent channel.

This issue is distinct from the free_response_channels / auto_thread configuration semantics discussed in #12750 and #15262. In this case, the config should have resulted in a thread, and the log shows an auto-thread creation failure.

Steps to Reproduce

  1. Configure Discord with auto-threading enabled:
discord:
  require_mention: false
  free_response_channels: ''
  allowed_channels: ''
  auto_thread: true
  1. Send a top-level message in a Discord server channel where Hermes is allowed to respond.
  2. Cause message.create_thread(...) and the fallback seed_msg.create_thread(...) path to fail, for example through a temporary Discord API / network failure.
  3. Observe that Hermes continues to run the agent and sends the final response inline in the parent channel.

Expected Behavior

If discord.auto_thread: true and the incoming top-level server-channel message is supposed to be routed to a newly created thread, failure to create that thread should be treated as a routing failure, not as permission to silently answer inline.

Preferred behavior:

  • Retry auto-thread creation a small number of times for transient Discord/network errors.
  • If thread creation still fails, do not invoke the agent for that user message.
  • Send a short visible error in the parent channel, for example: Hermes could not create a Discord thread for this message, so the request was not processed. Please retry.

This could be configurable, but the current silent inline fallback is surprising for thread-first Discord workflows.

Actual Behavior

Hermes logs the auto-thread failure, then proceeds to process the message and replies directly in the parent channel.

Example log:

2026-05-05 21:25:37,979 WARNING gateway.platforms.discord: [Discord] Auto-thread creation failed. Direct error: Cannot connect to host discord.com:443 ssl:default [None]. Fallback error: Cannot connect to host discord.com:443 ssl:default [None]

A later user message in a newly created thread worked again, which suggests the failure was transient. The confusing part is the intermediate inline response.

Affected Component

Gateway (Telegram/Discord/Slack/WhatsApp)

Messaging Platform (if gateway-related)

Discord

Debug Report

Not uploaded. Relevant local details:

Hermes Agent v0.12.0 (2026.4.30)
Project: /home/hansong/.hermes/hermes-agent
Python: 3.11.15
OpenAI SDK: 2.32.0
Current git commit: b816fd4e2

Operating System

Linux hermes 6.18.25-x64v3-xanmod1 x86_64 GNU/Linux

Python Version

Hermes runtime: Python 3.11.15 System python3: Python 3.13.5

Hermes Version

Hermes Agent v0.12.0 (2026.4.30), commit b816fd4e2

Additional Logs / Traceback (optional)

2026-05-05 21:25:37,979 WARNING gateway.platforms.discord: [Discord] Auto-thread creation failed. Direct error: Cannot connect to host discord.com:443 ssl:default [None]. Fallback error: Cannot connect to host discord.com:443 ssl:default [None]

Root Cause Analysis (optional)

In gateway/platforms/discord.py, _auto_create_thread() returns None after both direct thread creation and fallback seed-message thread creation fail:

async def _auto_create_thread(self, message):
    try:
        thread = await message.create_thread(...)
        return thread
    except Exception as direct_error:
        try:
            seed_msg = await message.channel.send(...)
            thread = await seed_msg.create_thread(...)
            return thread
        except Exception as fallback_error:
            logger.warning(...)
            return None

Then _handle_message() only switches routing when a thread object exists:

thread = await self._auto_create_thread(message)
if thread:
    parent_channel_id = str(message.channel.id)
    is_thread = True
    thread_id = str(thread.id)
    auto_threaded_channel = thread

If thread is None, processing continues with effective_channel = auto_threaded_channel or message.channel, so the message is routed to the parent channel and the agent response appears inline.

That makes auto-thread creation best-effort rather than a routing requirement.

Proposed Fix (optional)

Add a fail-closed path when auto-threading is enabled and applicable.

Possible design:

  1. Let _auto_create_thread() return a structured result instead of only thread | None, e.g. {thread, error}.
  2. In _handle_message(), when auto-threading was required for a top-level server-channel message and thread creation failed, send a short error to the parent channel and return before constructing / processing the MessageEvent.
  3. Optionally add a config flag for backwards compatibility, for example:
discord:
  auto_thread_failure_mode: error   # error | inline

Defaulting to error would match thread-first workflows better; defaulting to inline would preserve current behavior. Even if the default stays inline, a documented fail-closed option would be useful.

Retrying transient failures before giving up would also help:

  • retry message.create_thread(...) 2 times with short backoff
  • if all attempts fail, send the visible error and do not call the agent

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

extent analysis

TL;DR

Implement a fail-closed approach for auto-thread creation in Discord by returning a structured result from _auto_create_thread() and handling the error in _handle_message() to send a visible error instead of silently falling back to inline responses.

Guidance

  • Modify _auto_create_thread() to return a dictionary containing the thread object and any error that occurred, e.g., {thread, error}.
  • In _handle_message(), check if auto-threading was required and thread creation failed; if so, send a short error to the parent channel and return without processing the message further.
  • Consider adding a config flag auto_thread_failure_mode to allow users to choose between error and inline behavior.
  • Implement retry logic for transient failures, such as retrying message.create_thread(...) 2 times with short backoff before giving up and sending a visible error.

Example

async def _auto_create_thread(self, message):
    try:
        thread = await message.create_thread(...)
        return {'thread': thread, 'error': None}
    except Exception as direct_error:
        try:
            seed_msg = await message.channel.send(...)
            thread = await seed_msg.create_thread(...)
            return {'thread': thread, 'error': None}
        except Exception as fallback_error:
            return {'thread': None, 'error': fallback_error}

# In _handle_message()
thread_result = await self._auto_create_thread(message)
if thread_result['error'] and auto_threading_required:
    await message.channel.send('Hermes could not create a Discord thread for this message, so the request was not processed. Please retry.')
    return

Notes

The proposed fix assumes that the auto_thread configuration is correctly set and that the _auto_create_thread() function is the sole point of failure for thread creation. Additional error handling may be necessary depending on the specific requirements of the application.

Recommendation

Apply the proposed workaround by

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