hermes - 💡(How to fix) Fix Telegram DM topic bindings are not pruned when Bot API returns Thread not found

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…

Error Message

  1. In Telegram send fallback handling, when Bot API returns “Thread not found” or equivalent invalid-thread error:
  • simulate Telegram send error “Thread not found”

Root Cause

The recovery logic in gateway/run.py::_recover_telegram_topic_thread_id() intentionally rewrites an unknown/missing/lobby thread_id to the user's most recent known topic binding:

bindings = session_db.list_telegram_topic_bindings_for_chat(...)
...
for b in bindings:  # newest-first
    if str(b.get("user_id") or "") == user_id:
        recovered = str(b.get("thread_id") or "")
        if recovered and recovered != inbound:
            return recovered

This works for cross-topic replies, but it becomes harmful when the stored binding points to a Telegram topic that was deleted externally.

The Telegram adapter already observes the deletion indirectly when Bot API returns “Thread not found”, but the stale row remains in telegram_dm_topic_bindings.

Fix Action

Workaround

Manually delete stale rows from SQLite:

import sqlite3

conn = sqlite3.connect("/root/.hermes/state.db")
conn.execute(
    "DELETE FROM telegram_dm_topic_bindings WHERE chat_id=? OR user_id=?",
    ("<telegram_chat_id>", "<telegram_user_id>"),
)
conn.commit()
conn.close()

Then restart the gateway.

Code Example

WARNING gateway.platforms.telegram: [Telegram] Thread 15344 not found, retrying once with same thread_id
WARNING gateway.platforms.telegram: [Telegram] Thread 15344 not found, retrying without message_thread_id

---

INFO gateway.run: telegram topic recovery: chat=5595856929 user=5595856929 '15418' -> 15287

---

telegram_dm_topic_bindings:
chat_id=5595856929
thread_id=15287
user_id=5595856929
session_key=agent:main:telegram:dm:5595856929:15287

---

DELETE FROM telegram_dm_topic_bindings
WHERE chat_id = '5595856929' OR user_id = '5595856929';

---

bindings = session_db.list_telegram_topic_bindings_for_chat(...)
...
for b in bindings:  # newest-first
    if str(b.get("user_id") or "") == user_id:
        recovered = str(b.get("thread_id") or "")
        if recovered and recovered != inbound:
            return recovered

---

DELETE FROM telegram_dm_topic_bindings
WHERE chat_id = ? AND thread_id = ?;

---

import sqlite3

conn = sqlite3.connect("/root/.hermes/state.db")
conn.execute(
    "DELETE FROM telegram_dm_topic_bindings WHERE chat_id=? OR user_id=?",
    ("<telegram_chat_id>", "<telegram_user_id>"),
)
conn.commit()
conn.close()
RAW_BUFFERClick to expand / collapse

Bug Description

When Telegram DM topic mode is enabled, Hermes stores topic/session mappings in state.db (telegram_dm_topic_bindings). If a user manually deletes Telegram DM topics in the Telegram client, Hermes does not automatically prune the stale binding rows.

Later, when the user sends a message from a new Telegram topic, Hermes may treat the inbound thread_id as unknown and recover/redirect the session back to the stale topic binding. This causes tool progress, approvals, activity messages, and replies to appear in the wrong place or fall back to the main DM.

Observed Logs

After deleting Telegram topics manually, gateway logs showed:

WARNING gateway.platforms.telegram: [Telegram] Thread 15344 not found, retrying once with same thread_id
WARNING gateway.platforms.telegram: [Telegram] Thread 15344 not found, retrying without message_thread_id

Then later, after a new topic was created:

INFO gateway.run: telegram topic recovery: chat=5595856929 user=5595856929 '15418' -> 15287

The stale binding in SQLite was:

telegram_dm_topic_bindings:
chat_id=5595856929
thread_id=15287
user_id=5595856929
session_key=agent:main:telegram:dm:5595856929:15287

Deleting that row manually fixed the recovery behavior:

DELETE FROM telegram_dm_topic_bindings
WHERE chat_id = '5595856929' OR user_id = '5595856929';

Root Cause

The recovery logic in gateway/run.py::_recover_telegram_topic_thread_id() intentionally rewrites an unknown/missing/lobby thread_id to the user's most recent known topic binding:

bindings = session_db.list_telegram_topic_bindings_for_chat(...)
...
for b in bindings:  # newest-first
    if str(b.get("user_id") or "") == user_id:
        recovered = str(b.get("thread_id") or "")
        if recovered and recovered != inbound:
            return recovered

This works for cross-topic replies, but it becomes harmful when the stored binding points to a Telegram topic that was deleted externally.

The Telegram adapter already observes the deletion indirectly when Bot API returns “Thread not found”, but the stale row remains in telegram_dm_topic_bindings.

Expected Behavior

When Telegram send fails with “Thread not found” / invalid message_thread_id, Hermes should prune the corresponding stale topic binding from local state, e.g.:

DELETE FROM telegram_dm_topic_bindings
WHERE chat_id = ? AND thread_id = ?;

It should also consider removing the stale entry from channel_directory.json if present.

After pruning, _recover_telegram_topic_thread_id() should no longer redirect future messages to the deleted topic.

Actual Behavior

Hermes falls back to sending without message_thread_id, but the stale DB binding remains. Future inbound messages from a new topic can be recovered back to the stale thread.

Proposed Fix

  1. In Telegram send fallback handling, when Bot API returns “Thread not found” or equivalent invalid-thread error:

    • delete telegram_dm_topic_bindings row for (chat_id, thread_id)
    • optionally remove matching channel directory entry
    • log the pruning action
  2. Add a helper on SessionDB, e.g.:

    • delete_telegram_topic_binding(chat_id, thread_id)
  3. Add tests:

    • seed telegram_dm_topic_bindings with a stale topic
    • simulate Telegram send error “Thread not found”
    • assert the binding row is deleted
    • assert subsequent recovery no longer returns the stale thread
  4. Optional: expose a manual command:

    • /topic prune
    • /topic clear for users who delete topics manually.

Environment

  • Hermes Agent gateway running as systemd service
  • Platform: Telegram DM topic mode
  • OS: Linux
  • Relevant files:
    • state.db
    • table: telegram_dm_topic_bindings
    • gateway/run.py::_recover_telegram_topic_thread_id()
    • gateway/platforms/telegram.py send fallback path

Workaround

Manually delete stale rows from SQLite:

import sqlite3

conn = sqlite3.connect("/root/.hermes/state.db")
conn.execute(
    "DELETE FROM telegram_dm_topic_bindings WHERE chat_id=? OR user_id=?",
    ("<telegram_chat_id>", "<telegram_user_id>"),
)
conn.commit()
conn.close()

Then restart the gateway.

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

hermes - 💡(How to fix) Fix Telegram DM topic bindings are not pruned when Bot API returns Thread not found