Bug Description
Problem Statement
When the agent attempts to execute a dangerous command via the Telegram gateway:
- Approval request IS delivered — user sees "⚠️ Dangerous command requires approval..."
- User replies
/approve
- Result: total silence. The bot gives no response. The agent thread sits blocked until it times out.
This works correctly in the terminal CLI. It is broken specifically on Telegram (and likely all messaging platforms that route through the gateway's handle_message path).
Investigation Summary
Theories checked and ruled out
| Theory |
Status |
Reason |
HOME directory move (/opt/data/home) |
Ruled out |
Gateway starts fine, sessions/DB/config all load |
Config not found at $HERMES_HOME/config.yaml |
Ruled out |
HERMES_HOME=/opt/data, config at /opt/data/config.yaml — confirmed |
| Telegram adapter not running |
Ruled out |
Gateway lock present, messages flow both ways |
get_hermes_home() resolving wrong |
Ruled out |
Returns /opt/data, state.db exists at /opt/data/state.db |
| Approval notification not being sent |
Ruled out |
User confirmed receipt of the approval prompt |
Command not recognized as /approve |
Ruled out |
Telegram parses /approve as MessageType.COMMAND; get_command() strips @botname |
| Session key mismatch (registration vs resolution) |
Ruled out |
Both paths use _session_key_for_source(source) — keys matched |
Steps to Reproduce
- create test file in home: delete_me.txt
- ask agent from Telegram to delete file
- submit /approve
Expected Behavior
File should be deleted and agent resumes
Actual Behavior
agent waits for time out because approval is blocked
Affected Component
Gateway (Telegram/Discord/Slack/WhatsApp)
Messaging Platform (if gateway-related)
Telegram
Operating System
Ubuntu 24.04 server
Python Version
Python: 3.13.5
Hermes Version
Hermes Agent v0.7.0 (2026.4.3)
Relevant Logs / Traceback
Root Cause Analysis (optional)
Root Cause
Active-session guard in gateway/platforms/base.py intercepts /approve and /deny commands and queues them instead of dispatching them, creating a deadlock.
The code path
handle_message in gateway/platforms/base.py (line ~1023):
async def handle_message(self, event: MessageEvent) -> None:
session_key = build_session_key(event.source, ...)
if session_key in self._active_sessions:
# Session is busy — queue message and signal interrupt
self._pending_messages[session_key] = event # ← /approve lands HERE
self._active_sessions[session_key].set()
return # ← exits without dispatching
When the agent is running and hits a dangerous command:
- Agent thread blocks on a synchronous
threading.Event inside tools/approval.py
- Approval notification is sent to the user via Telegram
- User replies
/approve
- Telegram's
_handle_command builds a MessageType.COMMAND event and calls handle_message(event)
handle_message sees session_key in self._active_sessions is true
- The
/approve event is queued in _pending_messages and never dispatched
- The agent thread waits forever for approval that was silently queued
- The pending queued message won't be processed until the agent run completes
- The agent run can't complete because it's waiting for the approval
Classic deadlock. The agent waits for approval. The approval waits for the agent to finish.
Why CLI works
The terminal CLI uses synchronous input() via prompt_toolkit in the same process. No active-session guard, no message routing, no session key matching. The user's response goes directly to the blocking thread.
Why messaging platforms break
All gateway platforms (Telegram, Discord, WhatsApp, etc.) route messages through the base adapter's handle_message, which has the active-session guard. The guard treats /approve and /deny the same as any other message — it queues them. These control commands need to reach the gateway runner's command routing (run.py line 1828) to call resolve_gateway_approval() and unblock the agent thread.
Proposed Fix (optional)
Fix
File: gateway/platforms/base.py
Location: handle_message method, inside the active-session guard (line ~1023)
Change: Add command bypass for /approve and /deny
Before:
# Check if there's already an active handler for this session
if session_key in self._active_sessions:
# Special case: photo bursts/albums frequently arrive as multiple near-
# simultaneous messages. Queue them without interrupting the active run,
# then process them immediately after the current task finishes.
if event.message_type == MessageType.PHOTO:
After:
# Check if there's already an active handler for this session
if session_key in self._active_sessions:
# Gateway-level commands (/approve, /deny) must ALWAYS reach the
# gateway runner so they can resolve synchronous approval blockages.
# The agent thread is blocked on a threading.Event inside
# tools/approval.py — a soft interrupt won't unblock it. These
# commands bypass the active-session guard and are dispatched
# immediately. Without this, /approve gets queued silently and
# the agent times out waiting for user input that never arrives.
cmd = event.get_command()
if cmd in ("approve", "deny"):
logger.debug(
"[%s] Gateway command '%s' bypassing active-session guard for %s",
self.name, cmd, session_key,
)
task = asyncio.create_task(
self._process_message_background(event, session_key)
)
try:
self._background_tasks.add(task)
except TypeError:
return
if hasattr(task, "add_done_callback"):
task.add_done_callback(self._background_tasks.discard)
return
# Special case: photo bursts/albums frequently arrive as multiple near-
# simultaneous messages. Queue them without interrupting the active run,
# then process them immediately after the current task finishes.
if event.message_type == MessageType.PHOTO:
Are you willing to submit a PR for this?
Bug Description
Problem Statement
When the agent attempts to execute a dangerous command via the Telegram gateway:
/approveThis works correctly in the terminal CLI. It is broken specifically on Telegram (and likely all messaging platforms that route through the gateway's
handle_messagepath).Investigation Summary
Theories checked and ruled out
/opt/data/home)$HERMES_HOME/config.yamlHERMES_HOME=/opt/data, config at/opt/data/config.yaml— confirmedget_hermes_home()resolving wrong/opt/data,state.dbexists at/opt/data/state.db/approve/approveasMessageType.COMMAND;get_command()strips@botname_session_key_for_source(source)— keys matchedSteps to Reproduce
Expected Behavior
File should be deleted and agent resumes
Actual Behavior
agent waits for time out because approval is blocked
Affected Component
Gateway (Telegram/Discord/Slack/WhatsApp)
Messaging Platform (if gateway-related)
Telegram
Operating System
Ubuntu 24.04 server
Python Version
Python: 3.13.5
Hermes Version
Hermes Agent v0.7.0 (2026.4.3)
Relevant Logs / Traceback
Root Cause Analysis (optional)
Root Cause
Active-session guard in
gateway/platforms/base.pyintercepts/approveand/denycommands and queues them instead of dispatching them, creating a deadlock.The code path
handle_messageingateway/platforms/base.py(line ~1023):When the agent is running and hits a dangerous command:
threading.Eventinsidetools/approval.py/approve_handle_commandbuilds aMessageType.COMMANDevent and callshandle_message(event)handle_messageseessession_key in self._active_sessionsis true/approveevent is queued in_pending_messagesand never dispatchedClassic deadlock. The agent waits for approval. The approval waits for the agent to finish.
Why CLI works
The terminal CLI uses synchronous
input()viaprompt_toolkitin the same process. No active-session guard, no message routing, no session key matching. The user's response goes directly to the blocking thread.Why messaging platforms break
All gateway platforms (Telegram, Discord, WhatsApp, etc.) route messages through the base adapter's
handle_message, which has the active-session guard. The guard treats/approveand/denythe same as any other message — it queues them. These control commands need to reach the gateway runner's command routing (run.pyline 1828) to callresolve_gateway_approval()and unblock the agent thread.Proposed Fix (optional)
Fix
File:
gateway/platforms/base.pyLocation:
handle_messagemethod, inside the active-session guard (line ~1023)Change: Add command bypass for
/approveand/denyBefore:
After:
Are you willing to submit a PR for this?