Skip to content

fix(gateway): route plain-text approval responses (salvage #46924) - #55884

Merged
teknium1 merged 2 commits into
mainfrom
fix/plain-text-approval-routing-46924
Jun 30, 2026
Merged

fix(gateway): route plain-text approval responses (salvage #46924)#55884
teknium1 merged 2 commits into
mainfrom
fix/plain-text-approval-routing-46924

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Replying "yes" / "approve" / "deny" (plain text, no slash) now resolves a pending dangerous-command approval on messaging platforms — previously it deadlocked into an auto-deny.

Root cause: when the agent is blocked inside tools/approval.py waiting for approval, a bare-word reply fell through to the steer/queue/interrupt logic in _handle_active_session_busy_message. The reply got queued behind a turn that can't start until the approval resolves, so the approval timed out and auto-denied. Slash forms (/approve, /deny) already worked; bare words (what Signal/SMS users naturally type) did not.

Salvage of @liuhao1024's #46924 — their commit's authorship is preserved. Our follow-up commit reuses the canonical handlers and delivers the confirmation reply.

Changes

  • gateway/run.py: in _handle_active_session_busy_message, when has_blocking_approval(session_key) is true, route bare-word approval vocab (yes/approve/ok/y/confirm/deny/no/reject/cancel/n/always/session) through the existing /approve and /deny handlers — which resolve the waiting thread, resume typing, and return a localized confirmation — then deliver that confirmation to the user (it was silent before). Synthesizes a literal /-prefixed command so get_command_args() parses always/session on every platform (is_command() only recognizes /).
  • tests/gateway/test_plaintext_approval_routing.py: E2E tests over the real busy-handler path.

Why this location is correct

The base-adapter guard (gateway/platforms/base.py) invokes the busy-session handler before falling back to queueing, so plain text does reach this handler. The fix sits before the steer/queue logic and after the early-return guards (draining, internal synthetic events). The has_blocking_approval gate is the disambiguator — a conversational "yes" with no pending approval is never treated as command approval (preserving the design intent at run.py's "Pending exec approvals are handled by /approve and /deny" note).

Validation

Before After
Signal/SMS reply "yes" to approve queued → timeout → auto-deny resolves approval, command runs
User feedback after plain-text reply silent localized confirmation sent
always / session modifiers not parsed parsed via synthesized /approve <arg>
Conversational "yes" (no approval pending) n/a not consumed as approval

14 E2E tests green; adjacent approval/busy suites (test_approve_deny_commands.py, test_busy_session_ack.py) pass with no regressions.

Infographic

PR #46924 plain-text approval routing

Closes #46866.

liuhao1024 and others added 2 commits June 30, 2026 13:51
When the agent is blocked waiting for a dangerous-command approval,
plain-text responses like "yes" or "approve" were being steered into
the running agent instead of being delivered to the approval handler.
This meant approval via messaging platforms (Signal, Telegram, etc.)
never succeeded — the user's response was consumed by the steer logic
and the approval timed out.

Add an early check in `_handle_active_session_busy_message` that routes
approval-like responses ("yes", "approve", "deny", etc.) to the
approval handler when `has_blocking_approval()` is true for the session.

Fixes #46866

(cherry picked from commit b37ec1e)
…provals

Follow-up to liuhao1024's #46924. Route plain-text approval replies
through the canonical /approve and /deny handlers (resolve thread, resume
typing, return localized confirmation) and deliver that confirmation back
to the user — previously a plain 'yes' resolved silently. Synthesize a
literal '/'-prefixed command so get_command_args() parses always/session
modifiers on every platform (is_command() only recognizes '/'). Add E2E
tests covering approve/deny/always/session vocab plus the no-pending and
unrelated-text fall-through cases.
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jun 30, 2026
@teknium1
teknium1 merged commit dc3d435 into main Jun 30, 2026
31 checks passed
@teknium1
teknium1 deleted the fix/plain-text-approval-routing-46924 branch June 30, 2026 21:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Signal/Gateway: Approval responses are misrouted as steered mid-turn messages instead of being delivered to the approval handler

3 participants