hermes - 💡(How to fix) Fix [Feature]: Add pre_callback_query_dispatch plugin hook for Telegram inline keyboard callbacks

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…

Fix Action

Fix / Workaround

The same problem exists conceptually for any integration (polls, confirmation dialogs, reaction pickers, etc.) that wants to issue its own inline buttons and handle the resulting callbacks, without patching core files.

Add a pre_callback_query_dispatch hook that fires at the top of _handle_callback_query, before any built-in prefix routing, mirroring the existing pre_gateway_dispatch hook for message events.

"pre_callback_query_dispatch",

Code Example

"pre_callback_query_dispatch",

---

hook_result = await self._fire_plugin_hook(
    "pre_callback_query_dispatch",
    data=data,
    chat_id=str(query_message.chat_id) if query_message else None,
    user_id=str(query.from_user.id) if query.from_user else None,
    message_id=str(query_message.message_id) if query_message else None,
    raw_query=query,
)
if hook_result and hook_result.get("action") == "skip":
    return
RAW_BUFFERClick to expand / collapse

Problem or Use Case

Telegram inline keyboard button clicks (callback queries) are handled entirely inside _handle_callback_query in gateway/platforms/telegram.py. Today there is no way for a plugin to intercept a callback query before the built-in prefix checks run (mp:, ea:, sc:, etc.).

This makes it impossible to move custom callback logic out of telegram.py and into a plugin. For example, a news-feedback plugin needs to handle nf:yes:<id> / nf:no:<id> callbacks to record reader sentiment. Currently that logic must live directly in the gateway source file — tightly coupling application-specific behaviour to the core platform adapter. Any hermes update that overwrites telegram.py would silently discard that customisation.

The same problem exists conceptually for any integration (polls, confirmation dialogs, reaction pickers, etc.) that wants to issue its own inline buttons and handle the resulting callbacks, without patching core files.

Proposed Solution

Add a pre_callback_query_dispatch hook that fires at the top of _handle_callback_query, before any built-in prefix routing, mirroring the existing pre_gateway_dispatch hook for message events.

hermes_cli/plugins.py — register the new hook name in VALID_HOOKS:

"pre_callback_query_dispatch",

gateway/platforms/telegram.py — fire the hook at the entry point of _handle_callback_query:

hook_result = await self._fire_plugin_hook(
    "pre_callback_query_dispatch",
    data=data,
    chat_id=str(query_message.chat_id) if query_message else None,
    user_id=str(query.from_user.id) if query.from_user else None,
    message_id=str(query_message.message_id) if query_message else None,
    raw_query=query,
)
if hook_result and hook_result.get("action") == "skip":
    return

A plugin registers for this hook and returns {"action": "skip"} to claim the callback and prevent built-in handling, or returns {"action": "allow"} / None to let the existing logic proceed. The raw_query kwarg gives the plugin access to query.answer() and query.edit_message_reply_markup() for responding to Telegram.

This is a purely additive, non-breaking change. No existing callback handling is modified; the hook fires before any prefix check and only alters flow when a plugin explicitly returns {"action": "skip"}.

Alternatives Considered

  • Subclassing TelegramAdapter: Not practical — the adapter is instantiated internally and there is no injection point.
  • Monkey-patching _handle_callback_query in a plugin's on_session_start: Fragile, undocumented, breaks on any upstream refactor.
  • Keeping custom logic in telegram.py: Works today but is wiped on every hermes update, defeating the plugin system's purpose.

The hook approach matches exactly what pre_gateway_dispatch does for messages — consistent, documented, and testable.

Relationship to #21461

Issue #21461 introduced pre_gateway_dispatch (fired for every MessageEvent, including forum topic lifecycle events). This issue extends the same pattern to callback queries, which cannot be represented as MessageEvent objects and therefore cannot go through pre_gateway_dispatch. The two hooks are complementary: one covers messages, the other covers button interactions.

Feature Type

Gateway / messaging improvement

Scope

Small (single file, < 50 lines)

Contribution

I'd like to implement this myself and submit a PR.

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