Skip to content

fix: bypass active-session queue for /approve and /deny commands - #4911

Closed
kagura-agent wants to merge 1 commit into
NousResearch:mainfrom
kagura-agent:fix/approve-deny-deadlock
Closed

fix: bypass active-session queue for /approve and /deny commands#4911
kagura-agent wants to merge 1 commit into
NousResearch:mainfrom
kagura-agent:fix/approve-deny-deadlock

Conversation

@kagura-agent

Copy link
Copy Markdown
Contributor

Problem

When an agent session is actively running, BasePlatformAdapter.handle_message() in gateway/platforms/base.py queues all incoming messages instead of dispatching them immediately. This is correct for regular messages (they get processed after the current task), but /approve and /deny are control commands that signal a threading.Event in tools/approval.py to unblock the agent thread.

When these commands are queued instead of dispatched, the agent hangs indefinitely waiting for approval that will never arrive — a silent deadlock.

The handler in gateway/run.py (_handle_message()) already has early-intercept logic (line ~1829) that routes /approve and /deny directly to their handlers. But this code is never reached because handle_message() in base.py returns early after queuing the message.

Fix

Added a _CONTROL_COMMANDS frozenset to BasePlatformAdapter containing commands that must bypass the active-session queue:

  • approve, deny — signal approval threading.Event
  • stop — force-kill hung sessions
  • new, reset — session management

When handle_message() detects a control command during an active session, it spawns a lightweight fire-and-forget background task that calls the handler directly, instead of queuing the message.

The _process_message_background() method gains an is_control flag that skips session-lifecycle bookkeeping (interrupt events, pending message drain, _active_sessions cleanup) since another _process_message_background already owns the session.

Tests

Added 5 tests in tests/gateway/test_platform_base.py:

  • /approve dispatched during active session ✅
  • /deny dispatched during active session ✅
  • /stop dispatched during active session ✅
  • Regular text messages still queued during active session ✅
  • /approve@BotName suffix handled correctly ✅

Full test suite: 7704 passed (10 pre-existing failures, all unrelated).

Closes #4898

…sResearch#4898)

When an agent session is running, BasePlatformAdapter.handle_message()
queues incoming messages instead of dispatching them. This causes
/approve and /deny commands to be silently dropped — the agent thread
is blocked on a threading.Event in tools/approval.py waiting for
user input, but the control command never reaches the handler.

Add _CONTROL_COMMANDS frozenset to BasePlatformAdapter containing
commands that must bypass the active-session queue: approve, deny,
stop, new, reset. When handle_message() detects one of these during
an active session, it spawns a lightweight fire-and-forget background
task that calls the handler directly, instead of queuing the message.

The _process_message_background() method gains an is_control flag
that skips session-lifecycle bookkeeping (interrupt events, pending
message drain, _active_sessions cleanup) since another task already
owns the session.

Tests verify that /approve, /deny, /stop bypass the queue during
active sessions while regular text messages continue to be queued.
@dmthepm

dmthepm commented Apr 4, 2026

Copy link
Copy Markdown

This PR matches what I'm seeing on a live Telegram deployment (thoth, macOS/launchd).

Current user-visible symptom is often not "silent forever" but "/approve appears to do nothing for minutes".

From the logs on Thoth:

  • 2026-04-03 18:52:37,021 WARNING gateway.run: Interrupt recursion depth 3 reached ... queueing message instead of recursing.
  • 2026-04-03 19:23:59,744 ERROR gateway.run: Agent execution timed out after 600s ...

That strongly suggests the same underlying problem this PR addresses:

  • /approve is being treated like an in-band follow-up message instead of an out-of-band control command
  • it gets queued behind the active run
  • user only sees progress again after timeout / teardown

So from operational experience: yes, this is not theoretical. It is a real Telegram pain point in day-to-day use, especially when driving long Codex-style tasks over the gateway.

The one thing I'd explicitly watch in review is interaction with the existing interrupt-recursion path, because that's the warning line that consistently precedes the delayed approval behavior in this deployment.

@teknium1

teknium1 commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Closing — this was already fixed on main via PR #4926 (merged). The approach there uses direct inline dispatch via await self._message_handler(event) rather than spawning a competing background task, which avoids the race window described in #4926's analysis. /approve and /deny already bypass the active-session guard on main. Thanks for identifying the issue!

@teknium1 teknium1 closed this Apr 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Telegram /approve and /deny Commands Silently Drop During Active Agent Runs

3 participants