openclaw - ✅(Solved) Fix Telegram: plugin approval buttons don't work (handler missing plugin.approval.* events) [2 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
openclaw/openclaw#57339Fetched 2026-04-08 01:50:50
View on GitHub
Comments
1
Participants
2
Timeline
10
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×5closed ×1commented ×1locked ×1

Root Cause

Handler (Issue 1): `extensions/telegram/src/exec-approvals-handler.ts` line 361 — `handleGatewayEvent` only matches `exec.approval.*` events. Compare with Discord at `extensions/discord/src/monitor/exec-approvals.ts` line 665 which handles both.

Sequentializer (Issue 2): The Grammy runner queues all updates per chat. The agent message update holds the chat lock while waiting for approval. The callback_query for the same chat is queued behind it. Confirmed by adding a log at the very first line of the callback_query handler — it never executes during an active approval wait.

Fix Action

Fixed

PR fix notes

PR #57340: feat(telegram): handle plugin approval events in TelegramExecApprovalHandler

Description (problem / solution / changelog)

Summary

Fixes #57339 — adds full plugin approval support to Telegram, bringing it to parity with Discord. Tested end-to-end: approval prompt appears, button click resolves immediately, tool proceeds.

Commit 1: Handler subscription (exec-approvals-handler.ts)

TelegramExecApprovalHandler now handles plugin.approval.requested and plugin.approval.resolved gateway events.

  • Subscribe to plugin.approval.requested and plugin.approval.resolved in handleGatewayEvent
  • Accept ExecApprovalRequest | PluginApprovalRequest in handleRequested, shouldHandle, and helpers
  • Use buildPluginApprovalPendingReplyPayload for plugin approval messages
  • Accept ExecApprovalResolved | PluginApprovalResolved in handleResolved

Commit 2: Direct callback resolution (bot-handlers.runtime.ts)

Resolve approval callbacks directly via a lazily-initialized gateway client instead of routing through processMessage (which deadlocks because the session pipeline is blocked waiting for the approval decision).

  • Parse approval ID and decision from callback data
  • Call plugin.approval.resolve or exec.approval.resolve via gateway client
  • Return early — never falls through to processMessage

Commit 3: Bypass sequentializer (sequential-key.ts)

The @grammyjs/runner sequentializer processes all updates per chat sequentially. When the agent turn is waiting for approval, the callback_query is queued behind it — deadlock at the runner level.

Approval callback_queries now get a separate sequential key (telegram:{chatId}:approval), allowing them to be processed in parallel with the blocked agent turn. Same pattern as abort requests (telegram:{chatId}:control).

Commit 4: Dedup + param fix (exec-approvals-handler.ts, bot-handlers.runtime.ts)

  • For plugin approvals, skip handler delivery when the target matches the source chat (the inline session prompt already covers it). Handles telegram: prefix normalization.
  • Remove invalid resolvedBy property from plugin.approval.resolve call (gateway only accepts { id, decision }).

Commit 5: Review feedback (bot-handlers.media.ts, bot-handlers.runtime.ts, exec-approvals-handler.ts)

  • APPROVE_CALLBACK_DATA_RE now matches always alias (used by "Allow Always" button to fit Telegram's 64-byte callback_data limit)
  • parseApprovalCallbackDecision correctly maps alwaysallow-always (was allow-once)
  • Type cast replaced with minimal shim in resolveRequestSessionTarget

Commit 6: Tests (exec-approvals-handler.test.ts)

7 new plugin approval tests, all 3 existing exec approval tests remain green (10/10).

Test plan

Unit tests (10/10 passing)

  • Exec: sends approval to originating topic when target=channel
  • Exec: falls back to approver DMs when channel routing unavailable
  • Exec: clears buttons on resolved
  • Plugin: sends prompt with buttons to approver DM (different chat from source)
  • Plugin: dedup skips delivery when source matches target (same DM)
  • Plugin: delivers to approver DM when source is a different chat
  • Plugin: exec approvals NOT deduped (dedup only applies to plugin)
  • Plugin: clears buttons on plugin.approval.resolved
  • Plugin: plugin.approval.requested routed via handleGatewayEvent
  • Plugin: plugin.approval.resolved routed via handleGatewayEvent

Manual e2e (Telegram DM with ClawClip plugin)

  • Handler receives plugin.approval.requested events
  • Handler sends approval prompt with buttons to Telegram
  • Sequentializer allows callback_query through during active approval wait
  • Clicking "Allow Once" resolves the approval and tool proceeds
  • Single approval prompt (no duplicates in DM conversations)
  • No crash or gateway rejection on resolve call
  • Audit log correctly records the decision
  • All changes typecheck clean (0 new errors)
  • Verify group chat → DM forwarding (handler sends to approver DM when source is a group)

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • extensions/telegram/src/bot-handlers.media.ts (modified, +1/-1)
  • extensions/telegram/src/bot-handlers.runtime.ts (modified, +135/-0)
  • extensions/telegram/src/bot.create-telegram-bot.test-harness.ts (modified, +58/-0)
  • extensions/telegram/src/bot.test.ts (modified, +60/-0)
  • extensions/telegram/src/exec-approvals-handler.test.ts (modified, +103/-0)
  • extensions/telegram/src/exec-approvals-handler.ts (modified, +37/-21)
  • src/gateway/protocol/schema/exec-approvals.ts (modified, +1/-0)
  • src/gateway/protocol/schema/plugin-approvals.ts (modified, +1/-0)
  • src/gateway/server-methods/exec-approval.ts (modified, +7/-2)
  • src/gateway/server-methods/plugin-approval.test.ts (modified, +25/-0)
  • src/gateway/server-methods/plugin-approval.ts (modified, +7/-2)
  • src/gateway/server-methods/server-methods.test.ts (modified, +43/-1)
  • src/infra/exec-approval-session-target.ts (modified, +8/-2)

PR #57382: fix(telegram): bypass sequentializer queue for approval callback_queries

Description (problem / solution / changelog)

Summary

Approval callback_queries must bypass the per-chat sequential queue to avoid deadlock.

When a plugin approval prompt is sent to a user, the agent turn blocks waiting for approval. The Grammy runner sequentializer queues all updates per-chat, so the button click (callback_query) is blocked behind the agent turn — causing a deadlock.

Fix

sequential-key.ts: Approval callback_queries now route to a separate :approval key, running in parallel with the main agent pipeline (same pattern as existing :control bypass):

if (callbackData && /^\/approve(?:@[^\s]+)?\s+/i.test(callbackData)) {
  return `telegram:${chatId}:approval`;
}

bot-handlers.media.ts: APPROVE_CALLBACK_DATA_RE now accepts always as a valid decision value (alias for allow-always).

Scope

This PR only covers the sequentializer bypass. The full Telegram plugin approval fix requires two additional changes tracked in PR #57340:

  1. TelegramExecApprovalHandler plugin.approval.* event handling
  2. Direct gateway client resolve for callback queries (resolves part 1 deadlock without routing through session pipeline)

Files Changed

  • extensions/telegram/src/sequential-key.ts
  • extensions/telegram/src/bot-handlers.media.ts

Linked Issue

Fixes #57339 (partial — sequentializer bypass only; handler support is in PR #57340)

Changed files

  • extensions/discord/src/monitor/provider.lifecycle.test.ts (modified, +39/-0)
  • extensions/discord/src/monitor/provider.lifecycle.ts (modified, +6/-0)
  • extensions/telegram/src/bot-handlers.media.ts (modified, +1/-1)
  • extensions/telegram/src/sequential-key.ts (modified, +12/-1)
  • packages/memory-host-sdk/src/host/session-files.test.ts (modified, +36/-0)
  • packages/memory-host-sdk/src/host/session-files.ts (modified, +6/-1)
  • src/agents/pi-tools.ts (modified, +5/-1)
  • src/agents/tool-policy.test.ts (modified, +24/-0)
  • src/agents/tool-policy.ts (modified, +19/-2)
RAW_BUFFERClick to expand / collapse

Bug Description

Plugin approval buttons on Telegram don't work. There are two independent issues:

Issue 1: TelegramExecApprovalHandler missing plugin approval events

TelegramExecApprovalHandler.handleGatewayEvent() only handles exec.approval.requested/resolved, silently ignoring plugin.approval.requested/resolved. Discord's handler supports all four. Fix in PR #57340 commit 1.

Issue 2: Grammy runner sequentializer deadlocks callback_query processing

Even with the handler fix, clicking approval buttons does nothing. The @grammyjs/runner sequentializer processes all updates for a chat sequentially. When the agent turn is blocked waiting for approval, the callback_query from clicking the button is queued behind it at the runner level — the bot's callback handler never executes.

Exec approvals avoid this because they use a dedicated Unix socket (`exec-approvals.sock`), bypassing the Telegram update pipeline entirely.

Recommended fix: Add a pre-sequentializer middleware that intercepts approval `callback_query` updates (matching `APPROVE_CALLBACK_DATA_RE`) and resolves them directly via the gateway client, before they enter the per-chat sequential queue.

Steps to Reproduce

  1. Install a plugin that returns `requireApproval` from `before_tool_call` hook
  2. Configure `channels.telegram.execApprovals` with `enabled: true` and approver user ID
  3. Configure `approvals.plugin.enabled: true`
  4. Trigger a tool call that requires plugin approval via Telegram
  5. Click "Allow Once" button

Expected Behavior

Approval resolves immediately, tool proceeds.

Actual Behavior

  • Button shows "loading" briefly, nothing happens
  • Agent hangs until approval timeout (300s default)
  • After timeout: tool is denied

Root Cause Analysis

Handler (Issue 1): `extensions/telegram/src/exec-approvals-handler.ts` line 361 — `handleGatewayEvent` only matches `exec.approval.*` events. Compare with Discord at `extensions/discord/src/monitor/exec-approvals.ts` line 665 which handles both.

Sequentializer (Issue 2): The Grammy runner queues all updates per chat. The agent message update holds the chat lock while waiting for approval. The callback_query for the same chat is queued behind it. Confirmed by adding a log at the very first line of the callback_query handler — it never executes during an active approval wait.

Environment

  • OpenClaw: 2026.3.28
  • Channel: Telegram (Bot API, Grammy + @grammyjs/runner)
  • Plugin: ClawClip (returns `requireApproval` from `before_tool_call`)

extent analysis

Fix Plan

To resolve the issues with plugin approval buttons on Telegram, we need to address two independent problems:

  1. Modify the TelegramExecApprovalHandler to handle plugin.approval.requested/resolved events.
  2. Implement a pre-sequentializer middleware to intercept and resolve approval callback_query updates directly.

Step 1: Modify TelegramExecApprovalHandler

Update the handleGatewayEvent method in extensions/telegram/src/exec-approvals-handler.ts to handle plugin.approval.requested/resolved events:

handleGatewayEvent(event: GatewayEvent) {
  if (event.type === 'exec.approval.requested' || event.type === 'exec.approval.resolved' || 
      event.type === 'plugin.approval.requested' || event.type === 'plugin.approval.resolved') {
    // Handle the event
  }
}

Step 2: Implement Pre-Sequentializer Middleware

Create a new middleware that intercepts approval callback_query updates and resolves them directly:

import { Middleware, NextFunction, Context } from 'grammy';
import { APPROVE_CALLBACK_DATA_RE } from './constants';

const approvalMiddleware: Middleware = async (ctx: Context, next: NextFunction) => {
  if (ctx.callbackQuery && ctx.callbackQuery.data && APPROVE_CALLBACK_DATA_RE.test(ctx.callbackQuery.data)) {
    // Resolve the approval directly via the gateway client
    await ctx.gatewayClient.resolveApproval(ctx.callbackQuery);
    return;
  }
  await next();
};

export default approvalMiddleware;

Verification

To verify the fix, follow these steps:

  1. Install a plugin that returns requireApproval from before_tool_call hook.
  2. Configure channels.telegram.execApprovals with enabled: true and approver user ID.
  3. Configure approvals.plugin.enabled: true.
  4. Trigger a tool call that requires plugin approval via Telegram.
  5. Click the "Allow Once" button.

The approval should resolve immediately, and the tool should proceed.

Extra Tips

  • Make sure to test the fix thoroughly to ensure that it resolves the issues with plugin approval buttons on Telegram.
  • Consider adding logging to the middleware to track any issues or errors that may occur during the approval process.

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