Skip to content

feat(telegram): support secure Bot API Guest Queries - #62551

Open
milnerrad wants to merge 9 commits into
NousResearch:mainfrom
milnerrad:feat/telegram-guest-mode
Open

feat(telegram): support secure Bot API Guest Queries#62551
milnerrad wants to merge 9 commits into
NousResearch:mainfrom
milnerrad:feat/telegram-guest-mode

Conversation

@milnerrad

@milnerrad milnerrad commented Jul 11, 2026

Copy link
Copy Markdown

Summary

  • upgrade python-telegram-bot to 22.8 for Bot API Guest Query support
  • add a distinct, explicit allow_guest_queries opt-in that remains off by default and does not overload existing guest_mode
  • preserve Telegram user authorization and explicitly enforce configured chat/topic/own-message/trigger gates
  • rate-limit accepted intake per sender/chat with bounded state and deduplicate repeated guest_query_id updates
  • isolate each query in a synthetic one-shot session lane
  • deliver exactly one formatted, length-bounded text answer through answerGuestQuery
  • suppress typing, streaming, progress, media/file sends, TTS, normal-send fallback, and duplicate final delivery
  • fail closed for interactive clarification and command approval: no normal sendMessage; clarification returns control to the agent and approval-required operations are denied
  • block the cross-channel send_message tool—including list/send/react/unreact—during Guest Query turns so it cannot bypass answerGuestQuery
  • return a clear text-only answer for unsupported guest media/location payloads
  • document configuration, limitations, and full-tool trust implications in English and Chinese

Security model

Bot API Guest Queries can originate from chats where the bot is not a member, so support is disabled unless the operator explicitly enables allow_guest_queries (or TELEGRAM_ALLOW_GUEST_QUERIES=true). Enabling it does not authorize new users: normal Telegram user authorization still runs, configured allowed_chats is enforced explicitly for PTB's special sender chat type, and topic/own-message/trigger gates remain active.

Accepted Guest Queries otherwise use the same configured agent and tools as other authorized Telegram turns. They are session-isolated but are not a general reduced-permission tool sandbox. The cross-channel send_message tool is blocked to preserve the one-shot delivery boundary. The one-shot transport cannot host interactive clarification or approval buttons; operations requiring approval are denied automatically.

Configuration

gateway:
  platforms:
    telegram:
      extra:
        allow_guest_queries: true
        guest_query_rate_limit_per_minute: 5

The rate limit is per sender/chat, clamped to 1–60 accepted queries per minute. State is bounded to prevent unbounded key growth.

Verification

Rebased onto current main. The sole conflict was in pyproject.toml; resolution preserves upstream aiohttp==3.14.3 and this PR's PTB 22.8 requirement.

  • comprehensive focused selection: 62 files, 626 passed, 0 failed
    • all tests/gateway/test_telegram*.py
    • changed proxy/run/recovery modules
    • approval and clarification contract suites
    • project metadata/pin invariants
  • real PTB 22.8 Update.de_json() Guest Message intake probe: passed
  • adjacent test_platform_base.py: 84 passed, 1 skipped, 1 failed; the same credential-path test fails identically on untouched current main and is pre-existing
  • uv lock --check: passed; PTB resolves to 22.8 and upstream aiohttp remains 3.14.3
  • Ruff, Python compilation, static security scan, and git diff --check: passed

Review follow-up

This revision addresses the prior findings: handler ordering; explicit default-off trust-boundary gating; user/chat/topic scope; rate limiting and deduplication; long-answer formatting and bounded delivery; one-shot runner/session propagation; suppression of normal-send auxiliary paths; fail-closed approval/clarification handling; Guest media/location behavior; and lazy-dependency pin consistency.

@milnerrad
milnerrad requested a review from a team July 11, 2026 09:03
@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery platform/telegram Telegram bot adapter dependencies Pull requests that update a dependency file P3 Low — cosmetic, nice to have duplicate This issue or pull request already exists labels Jul 11, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #32802 (earliest open Telegram Guest Mode PR). Both implement the same Bot API 10.0 mechanism: subscribe to guest_message updates, thread the guest query id, and reply once via answerGuestQuery/answer_guest_query while suppressing intermediate sends. #43049 (the earliest comprehensive impl), #51082, and #59879 are all closed, leaving #32802 as the canonical open version. This PR additionally bumps python-telegram-bot 22.6->22.8 and adds a live-verification note. Related: #46196 (disable streaming for guest replies).

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the focused Guest Mode implementation and the dedicated regression coverage.

Problems

  • Blocking — plugins/platforms/telegram/adapter.py:3229: TypeHandler(Update, self._handle_guest_message) is registered in group=-1. That handler matches every Update; for a normal update _handle_guest_message returns, but the existing text/command/media handlers are all in the default group at plugins/platforms/telegram/adapter.py:3173-3190 and are no longer reached. This disables normal Telegram intake when the Guest Mode symbols are present. The related open implementation in #32802 places its catch-all TypeHandler in group 1, after the normal handlers.

Suggested changes

  • Move this handler to a later group (or make its check update-specific), keeping the generic-handler guest guards for deduplication.
  • Add a handler-registration regression test covering both an ordinary text update and a guest update with Guest Mode symbols enabled.

Automated hermes-sweeper review.

Comment thread plugins/platforms/telegram/adapter.py Outdated
@milnerrad

Copy link
Copy Markdown
Author

Done. I've moved the catch-all from to a later fallback as suggested, matching the sibling PR #32802.

On python-telegram-bot's group dispatch rules:
We confirmed empirically that because PTB evaluates each handler group independently, registering the catch-all in did not actually swallow ordinary updates (since groups are processed in ascending order and only stop if a handler raises ). However, moving it to is a much cleaner architecture: normal intake in the default group (0) now has structural priority, and the catch-all only acts as a strict fallback.

Regression Coverage:
I extracted the handler-registration block out of the active-polling initialization path into a testable helper, and added corresponding regression tests to :

  1. — Asserts that normal MessageHandlers remain in and the guest catch-all is registered exactly once in a strictly later group.
  2. — Asserts the fallback branch where Guest Mode is missing entirely from PTB symbols.
  3. — Simulates PTB's group-dispatch loop over the actual registration layout, asserting that an ordinary text update reaches its group-0 text handler and is never hijacked by the guest fallback, while a guest update routes to the guest handler.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 11, 2026
@abner-augusto

Copy link
Copy Markdown

I want to add one important clarification from the history around #46196.

That issue was opened specifically because Telegram guest replies still needed one-shot final delivery via answerGuestQuery, with streaming/interim sends suppressed. At least one earlier PR attempt was closed as a duplicate before main actually had the underlying guest-message mechanism implemented end-to-end, so the duplicate classification there ended up being misleading in practice: the bug described in #46196 remained unresolved on main.

Re-checking current main, that still appears to be the case:

  • no complete guest-reply one-shot path on the relevant Telegram/gateway surfaces
  • no merged answerGuestQuery / guest-query propagation path covering the reported symptom

For #62551 specifically, the earlier review blocker about TypeHandler(Update, ...) preempting normal intake appears to have been addressed in the follow-up commit (fallback group + regression coverage).

So I think the actionable question now is no longer “is this a duplicate?” but rather:

  1. is #62551 the intended consolidation vehicle for #46196, or
  2. should this be folded into another still-active PR branch?

Either way, the issue itself still looks open on main, so it would be useful to resolve the consolidation path explicitly rather than leaving it ambiguous under duplicate labeling.

@alt-glitch alt-glitch added comp/plugins Plugin system and bundled plugins and removed duplicate This issue or pull request already exists sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data labels Jul 30, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Correction: #32802 is closed and cannot be a duplicate anchor. #62551 is the active end-to-end Guest Mode candidate; it is related to #46196 and #32802, not a duplicate.

@GottZ

GottZ commented Jul 30, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

You are asking how this repository should handle duplicate and consolidation status when the underlying fix is still absent from main.

Case context, measured live from our triage graph (2026-07-30T11:57:01+00:00):

If you want to move this one along: keep the diff scoped, rebase onto current main so the change is cheap to verify, and if one of the overlapping PRs above already lands your behaviour, say so explicitly — a clean supersede is faster to confirm than a fresh review.

@abner-augusto

Copy link
Copy Markdown

I rebased #62551 locally onto current main, resolved the merge conflicts, and ran the Telegram gateway suite against that rebased branch.

For the maintainer/author side: the rebase conflicts were small and localized. In my local rebase they were limited to:

  • gateway/platforms/base.py
  • pyproject.toml
  • uv.lock

So this does not look blocked by a large structural rework — it looks salvageable from a rebase/refresh standpoint.

Automated validation looked good:

  • scripts/run_tests.sh tests/gateway/test_telegram*.py
  • 54 files, 513 tests passed, 0 failed
  • focused guest suite also passed

But a live Guest Mode repro still found two concrete issues, so I don't think this is merge-ready yet.

  1. Long guest replies still fail on the real answerGuestQuery path.

    • Repro: a private guest DM with a longer answer
    • Observed user-visible fallback: (Response formatting failed, plain text:) ...
    • Gateway log:
      • Failed to answer Telegram guest query
      • telegram.error.BadRequest: Message_too_long
      • Send failed: Message_too_long — trying plain-text fallback
    • Code path: plugins/platforms/telegram/adapter.py:4338-4356
    • The guest final-send path calls answer_guest_query() with InputTextMessageContent(content.strip()) directly, without pre-truncation/chunking, so long guest replies overflow before the guest reply path can succeed.
  2. A guest reply from a group where the bot is not a member still appears to leak at least one normal send attempt.

    • Repro: guest mention from a group the bot is not in
    • User-visible result: the short guest answer did appear
    • But the gateway log also emitted:
      • Failed to send Telegram message: Forbidden: bot was kicked from the group chat
      • twice
    • That strongly suggests the one-shot guest answer succeeded, but some auxiliary path still attempted a normal sendMessage into the underlying group surface.

I also compared the rebased PR structure with an earlier local guest-mode patch I had tested against the same issue. One important difference is that this PR currently carries guest plumbing only in:

  • plugins/platforms/telegram/adapter.py
  • gateway/platforms/base.py

and not in:

  • gateway/run.py
  • gateway/session.py

That matches the live symptom class pretty well: the final adapter hook is there, but guest reply state is not propagated strongly enough through the runner/session layer to guarantee that every auxiliary/interim/stream path stays off normal Telegram sends.

So my read is:

  • #62551 is still worth keeping alive
  • the rebase conflicts themselves look manageable
  • but it is not ready to merge yet

The two concrete follow-ups seem to be:

  1. truncate/chunk guest final replies before answer_guest_query() so they don't fail with Message_too_long
  2. propagate guest reply identity through runner/session state (or otherwise hard-disable all auxiliary/interim/stream side paths for guest turns) so nothing falls back to normal sendMessage on guest-originated turns

Happy to share the exact rebase/test setup if useful.

@alt-glitch alt-glitch added the needs-decision Awaiting maintainer decision before any implementation label Jul 30, 2026

@GottZ GottZ left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

Verdict

Request changes. The Guest Mode plumbing itself is competent and narrowly written — the one-shot lane, the synthetic guest:<query_id> thread id, and the group-1 handler placement are all sensible, and the follow-up commit 3464527 genuinely resolves @teknium1's group=-1 blocker (verified: TypeHandler(Update, self._handle_guest_message) is now registered at group=1, adapter.py:7563, with three registration tests behind it). What blocks merge is not the mechanism but its trust boundary: this is the first code path on the Telegram gateway that lets a user the operator has never allowlisted, in a chat the bot is not a member of, dispatch a full agent turn — and after merge it is on by default with no env var, no config key and no documentation. Two blockers and five majors below are about that boundary and about the egress paths it does not yet cover; the rest are scoping and coverage points.

What I verified against head 3464527

I did not review from the diff text alone. Every claim below was checked against the head worktree and, where PTB behaviour was load-bearing, against the real python-telegram-bot 22.8 wheel (extracted, imported, telegram.__version__ == 22.8) rather than the repo's test mock — which matters, because the gateway conftest's telegram mock makes hasattr(Update, "GUEST_MESSAGE") true unconditionally and would have hidden the difference.

Reproduced in-process against the head adapter with real PTB 22.8 on sys.path:

  • the handler-registration condition (adapter.py:7545-7550) evaluates all four terms true once this PR's dependency bump is in place;
  • 'guest_message' in Update.ALL_TYPES is True, and the adapter requests allowed_updates=Update.ALL_TYPES on all four intake paths (:2031, :2141, :2543, :3310);
  • the guest send path's behaviour with a 9000-character payload, with _send_path_degraded = True, and with whitespace-only content;
  • what answer_guest_query's real return object does and does not carry;
  • which PTB filters match a guest location / photo update, and what Update.effective_message resolves to for them;
  • the effective authorization verdict for the exact message shape the new happy-path test uses, under a strict TELEGRAM_ALLOWED_USERS.

Test runs (venv with pytest, scripts/run_tests.sh selection):

tree files result
head 3464527, tests/gateway/test_telegram_guest_mode.py 1 11 passed in 0.28s
head 3464527, tests/gateway/test_telegram*.py 48 3 failed, 1046 passed in 45.57s
merge-base 3b2ef789d, same selection 47 3 failed, 1033 passed in 45.29s
origin/main b4f8c491d + this PR (conflicts resolved locally) 54 15 failed, 498 passed in 24.33s
bare main 466e6402f, same selection 53 15 failed, 485 passed in 27.73s

The 1,046 passed figure in the description reproduces exactly on the stale base. The 3 failures I see on the head are the same three test_telegram_thread_fallback.py tests that fail identically on the merge-base under the full-selection run and pass in isolation — order-dependent and pre-existing, not caused by this PR. The 15 failures on the merged tree are ModuleNotFoundError: No module named 'wcwidth' in my environment and are present on bare main too. Net effect of the PR on both comparisons: +13 passing, 0 new failures — exactly the 11 new guest tests plus the 2 new test_telegram_reply_mode.py cases. The suite result is genuinely clean; only the numbers in the description are stale, because main has pruned these files considerably.

Merge state

mergeable=CONFLICTING / mergeStateStatus=DIRTY. Merge base 3b2ef789d is 4441 commits behind origin/main (b4f8c491d). git merge-tree produces conflicts in exactly three files — gateway/platforms/base.py, pyproject.toml, uv.lock — while plugins/platforms/telegram/adapter.py and tests/gateway/test_telegram_reply_mode.py auto-merge clean. This confirms @abner-augusto's independently reported list, file for file. Importantly, the conflicting main state does not invalidate the PR's premises: main is still on PTB 22.6 (so the 22.6→22.8 bump is uncontested), pyproject.toml conflicts only over adjacent moved pins (starlette 1.0.1→1.3.1, slack-bolt 1.27.0→1.29.0, slack-sdk 3.40.1→3.43.0), and git grep -i 'guest_message\|answer_guest_query\|guest_query' over origin/main returns zero hits — no Bot-API-10.0 guest code exists on main, so nothing here has been superseded. One conflict is more than mechanical and is called out inline.

Relation to the existing reviews and to the complex

  • @teknium1 / hermes-sweeper review 4677972075 (review-verdict=keep_open salvageability=medium): its single blocker is resolved at this head. Not re-raised.
  • @abner-augusto (comment 5131474384) did the most valuable prior work here — a real rebase plus a live Guest Mode repro. I confirm both of his findings at code level and his structural diagnosis is correct: telegram_guest_query_id is consulted in exactly one place in the entire tree (adapter.py:3583), with zero occurrences in gateway/run.py, gateway/session.py or gateway/delivery.py. His Message_too_long finding I therefore treat as already open and do not re-litigate; where my inline notes touch the same lines they add the parts he did not cover, and his question "which auxiliary path still sends?" is answered concretely inline.
  • @alt-glitch's duplicate classification against #32802 was retracted by its own author on 2026-07-30, correctly: our graph confirms #32802 CLOSED 2026-07-13, and #43049 / #51082 / #59879 / #68061 / #22263 all closed. #62551 is the only open PR in the complex (root issue #46196, still OPEN). It is the consolidation vehicle — @abner-augusto's framing of the actionable question is the right one.
  • Our own earlier comment (5130675377) asked for a scoped diff and a rebase onto current main. Nothing here contradicts that; the inline notes are consistent with it.
  • Complex neighbours worth a maintainer's eye: #527 (Gateway Permission Tiers — Owner/Admin/User/Guest) and #16017 (owner/guest/unknown/banned tiers) are both OPEN RFCs about precisely the user class this PR introduces. Combined with the needs-decision label already applied, the default-on question should be settled by a maintainer rather than by a dependency bump. The P3 — Low, cosmetic, nice to have label looks hard to reconcile with a change to who can reach the agent, and the three sweeper:risk-* labels (session-state, message-delivery, compatibility) all fired accurately.

What is good and should survive the rework

Worth stating plainly, because the rework list is long: using the per-summon guest_query_id as the session lane is the right call — it gives key-level isolation from owner sessions for free. _numeric_message_thread_id is a clean fix with real tests. Extracting _register_handlers for testability was the right response to the earlier review. The compatibility fallbacks for missing PTB symbols are careful. Eleven focused tests for a feature of this size is above the bar for this queue.

(Note for the maintainer applying this: this reviewer cannot set a formal REQUEST_CHANGES state on this repository — please read the verdict above as the substantive one.)

Comment thread plugins/platforms/telegram/adapter.py
Comment thread plugins/platforms/telegram/adapter.py Outdated
Comment thread plugins/platforms/telegram/adapter.py
Comment thread plugins/platforms/telegram/adapter.py Outdated
Comment thread plugins/platforms/telegram/adapter.py
Comment thread plugins/platforms/telegram/adapter.py
Comment thread gateway/platforms/base.py
Comment thread plugins/platforms/telegram/adapter.py Outdated
Comment thread tests/gateway/test_telegram_guest_mode.py
Comment thread plugins/platforms/telegram/adapter.py Outdated
@milnerrad
milnerrad marked this pull request as draft August 8, 2026 05:12
@milnerrad
milnerrad force-pushed the feat/telegram-guest-mode branch from 3464527 to 9790f52 Compare August 8, 2026 05:58
@milnerrad milnerrad changed the title feat(telegram): support guest mode queries feat(telegram): support secure Bot API Guest Queries Aug 8, 2026
@milnerrad
milnerrad marked this pull request as ready for review August 8, 2026 05:58
@milnerrad
milnerrad force-pushed the feat/telegram-guest-mode branch from 9790f52 to cbdc06a Compare August 9, 2026 01:27
@milnerrad

Copy link
Copy Markdown
Author

Rebased this PR onto current main and refreshed the implementation after resolving the sole conflict in pyproject.toml (preserving upstream aiohttp==3.14.3 while retaining this PR's python-telegram-bot==22.8).

The current revision addresses the earlier review findings:

  • Guest Query intake is explicitly disabled by default.
  • Existing Telegram user authorization plus configured chat/topic/trigger gates remain enforced.
  • Accepted intake is rate-limited per sender/chat and duplicate guest_query_id updates are suppressed with bounded state.
  • Guest identity is propagated through runner/platform metadata so streaming, typing, progress, media/file, TTS, and normal-send fallback paths remain suppressed.
  • Final delivery uses one answerGuestQuery call. Oversized output is length-bounded with an explicit truncation notice rather than failing with Message_too_long.
  • Interactive clarification and command-approval prompts are now fail-closed for the one-shot transport: they never call normal sendMessage; clarification returns control to the agent, and approval-required operations are denied automatically.
  • The cross-channel send_message tool is blocked for Guest Query turns—including list, send, react, and unreact actions—so it cannot bypass answerGuestQuery or expose connected messaging targets.
  • The lazy-install PTB pin now matches pyproject.toml and uv.lock at 22.8, preventing update-time downgrade churn.
  • Documentation states the full-tool trust boundary and the non-interactive approval/clarification limitation.

Fresh verification against the rebased range:

  • comprehensive focused selection: 64 files, 679 passed, 0 failed
    • includes all tests/gateway/test_telegram*.py
    • changed proxy/run/recovery modules
    • approval and clarification contract suites
    • send_message Guest Query boundary tests
    • project metadata/pin invariants
  • real python-telegram-bot==22.8 Update.de_json() Guest Message intake probe: passed
  • adjacent test_platform_base.py: 84 passed, 1 skipped, 1 failed; the same credential-path test fails identically on untouched current main, so it is a pre-existing baseline failure
  • uv lock --check: passed; lock resolves PTB 22.8 and preserves upstream aiohttp 3.14.3
  • Ruff, Python compilation, static security scan, and git diff --check: passed

This branch remains the active end-to-end candidate for the still-unimplemented Guest Query path on main.

@alt-glitch alt-glitch added area/streaming Streaming responses: gateway delivery, provider wire sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data comp/tools Tool registry, model_tools, toolsets labels Aug 9, 2026
@milnerrad
milnerrad force-pushed the feat/telegram-guest-mode branch from cbdc06a to 5079108 Compare August 16, 2026 06:47
@milnerrad

Copy link
Copy Markdown
Author

Refreshed this branch onto current origin/main (406c5daf). The reconciliation preserves upstream’s centralized Telegram handler registration and gateway_platform_event observer (group 99), keeps Guest Query as a group-1 fallback, and includes the later one-shot secrecy hardening. Verification: 210 focused Telegram/gateway/tool tests passed; Ruff and git diff --check passed.

Address blocking review comment. Moving the catch-all TypeHandler(Update)
from group=-1 to fallback group=1 ensures it does not compete with normal
text/command/media handlers in the default group (0).

While PTB's group-dispatch rules evaluate groups independently (so a group=-1
catch-all does not prevent default group-0 handlers from running unless a
handler raises ApplicationHandlerStop), this movement matches the placement in
sibling PR NousResearch#32802 and keeps registration clean.

Extracted adapter.py handler registration into _register_handlers() and added
accompanying regression tests in test_telegram_guest_mode.py to assert both
group-sorting and dispatch routing.

(cherry picked from commit ae489fd)
Render final guest replies through the existing MarkdownV2 formatter before answerGuestQuery and cover headings, bold text, and GFM tables.

(cherry picked from commit 0be553a)
@milnerrad
milnerrad force-pushed the feat/telegram-guest-mode branch from 5079108 to 83a7eb8 Compare August 17, 2026 01:50
@milnerrad

Copy link
Copy Markdown
Author

Rebased and security-reviewed this branch onto current upstream/main (bab7be3ca) and force-pushed the refreshed 9-commit series (83a7eb8b2). The remote head and upstream tip were fetched immediately before push; neither had moved, and the push used an explicit lease against old head 507910843c948e2b25ca2a8f8fe9cc3d897913cb.

Blocker → fix checklist

  • Catch-all handler preempting normal Telegram intake → Guest Query stays a strict group-1 fallback; ordinary updates retain group-0 priority and regression coverage.
  • Untrusted/default-on intake → disabled by default; explicit opt-in required.
  • Authorization/sender binding → existing Telegram user/chat/topic/trigger gates remain enforced; sender identity and query metadata are validated before enqueue.
  • Replay/abuse → bounded duplicate-query suppression plus per-sender/chat rate limiting.
  • Guest identity lost through the runner → carried in source/thread metadata across gateway/platform event reconciliation.
  • Auxiliary/normal Telegram egress → streaming, typing, progress, media/file/TTS, edit/delete, retry, and plain-text fallback paths are suppressed for Guest Query turns.
  • One-shot final delivery → exactly one answer_guest_query attempt; failures are non-retryable and never fall through to sendMessage.
  • Oversized/empty replies → UTF-16-length-bounded formatted answer with explicit truncation; whitespace-only output does not burn the endpoint.
  • Interactive approval/clarification → fail closed without normal-message prompts.
  • Cross-channel tool bypass → Guest Query turns cannot list/send/react/unreact through send_message.
  • Credential/PII leakage on transport failure → Telegram transport errors use the platform redactor. The new top commit also prevents agent BaseException details/tracebacks from crossing the Guest Query boundary or entering logs, while preserving the existing non-guest behavior unchanged.
  • Runtime dependency contract → verified against real python-telegram-bot==22.8; Bot.answer_guest_query(self, guest_query_id, result, ...) imports as an async method returning SentGuestMessage.

Verification

  • Final focused batch: 59 files, 692 tests passed, 0 failed.
  • Post-fix targeted rerun: 2 files, 48 tests passed, 0 failed (test_tool_response_drop_recovery.py, test_telegram_guest_mode.py).
  • Ruff on the security-fix files: passed.
  • Python compilation of the changed runtime/security surfaces: passed.
  • git diff --check: passed.
  • Real PTB 22.8 runtime import/signature probe: passed.

@teknium1 @GottZ @abner-augusto — could you please re-review the refreshed head, especially the resolved trust-boundary and one-shot-delivery blockers?

@abner-augusto

Copy link
Copy Markdown

I tested the latest #62551 revision locally, using an isolated worktree and virtual environment so the test did not touch my local branch or its uncommitted changes.

Test worktree:

  • Branch: test/pr-62551-latest-20260817
  • HEAD: 83a7eb8b25ace86536aeb4d6ba1d11a0436f30b1
  • Dependencies: uv sync --extra dev --extra messaging completed successfully

Automated verification:

  • pytest -q tests/gateway/test_telegram_guest_mode.py35 passed
  • ruff check on the changed Guest Mode/runtime files → passed
  • scripts/run_tests.sh tests/gateway/test_telegram*.py55 files, 589 passed, 0 failed
  • Additional changed-area tests → 148 passed, 0 failed

I also enabled gateway.platforms.telegram.extra.allow_guest_queries: true in the active config and ran a live test with the gateway using the isolated PR worktree.

The live Guest Mode tests succeeded:

  1. Group Guest Query

    • A guest mention from a group where the bot is not a member produced a visible answer.
    • I did not see the previous Forbidden: bot was kicked from the group chat leakage.
  2. Long private Guest Query

    • The agent processed a Hanyu Pinyin question and returned a response of about 9,960 characters before Telegram delivery formatting.
    • The final response was delivered successfully.
    • There were no Message_too_long, Forbidden, retry, or traceback leakage errors.

The fixes appear to address the two runtime problems I previously reported:

  • oversized Guest Query delivery no longer fails the request;
  • auxiliary/normal-send paths no longer leak into the underlying group chat.

I did notice two remaining UX concerns during the live test.

1. There is no visible processing indication

The long Guest Query took approximately 53 seconds from intake to final delivery. During that time, the user receives no visible indication that the bot accepted the Guest Query and is working on it.

The logs show the full gap clearly:

10:32:00 inbound message
10:32:01 conversation turn
10:32:52 turn ended
10:32:53 response ready
10:32:53 Sending response (9960 chars)

I understand why ordinary interim messages and streaming cannot be used here: Guest Mode is a one-shot delivery path and must not consume the guest_query_id before the final answer.

Could we check whether a non-message sendChatAction("typing") indicator is safe for the Guest Query caller while the agent is running? If Telegram does not permit that for the guest caller context, it would be useful to document the limitation or include a clear marker in the final response.

2. Long answers are truncated rather than compacted

The final message displayed:

[Response truncated for Telegram Guest Mode]

This is working as a safety fallback, but the original answer was about 9,960 characters and the final Telegram message is bounded by MAX_MESSAGE_LENGTH = 4096. Therefore, the tail of the answer — potentially including the conclusion — is discarded.

Since Guest Mode cannot reliably send two separate messages with the same one-shot query, I would avoid splitting the answer into multiple answerGuestQuery calls. A second call could consume an already-used/expired guest_query_id, and falling back to normal sendMessage is not valid when the bot is not a member of the source chat.

A better UX would be to compact the answer before the final Guest Query delivery, keeping the direct answer, essential distinctions, important examples, and conclusion within a safe margin below Telegram's limit. The current hard truncation should remain as a final safety net, but ideally it should be exceptional rather than the normal behavior for long answers.

For example, the Guest Mode finalization path could use a targeted instruction such as:

Rewrite this answer for Telegram Guest Mode.
Keep the final response below the platform message limit with a safety margin.
Preserve the direct answer, key distinctions, essential examples, and conclusion.
Remove repetition and optional background detail.
Return only the compact final answer.

Overall, the latest revision passed the requested automated and live validation, and the core Guest Mode delivery path now looks solid. My remaining recommendations are limited to the processing indication and pre-delivery compaction of long responses.

@milnerrad

Copy link
Copy Markdown
Author

Thank you for the thorough isolated automated and live validation. The 589 Telegram tests plus the changed-area checks, and especially the successful group and long private Guest Query runs, give useful confidence that the one-shot delivery and secrecy fixes are working as intended.

I agree the two remaining points are UX opportunities rather than blockers for this fix:

  1. Processing indication: Guest Mode’s defining case is that the bot may not be a member of the source chat. A normal chat action would therefore depend on the same unavailable chat context that this one-shot path deliberately avoids, and it must not introduce a fallback that can leak into the underlying chat. I’m keeping this PR’s invariant of no auxiliary sends.
  2. Long-answer compaction: A second model pass during finalization would add more latency, cost, and a new failure mode to an already ~53-second request. The current bounded truncation is deterministic and preserves the one-shot guarantee. A better follow-up would constrain Guest Mode output during the original generation/finalization contract, with hard truncation retained as the final safety net, rather than add another delivery-time model call here.

I’m therefore keeping this PR scoped to the validated correctness and secrecy fixes and treating both recommendations as non-blocking follow-up design work. Thanks again for exercising the real Bot API path.

@alt-glitch alt-glitch added area/config Config system, migrations, profiles comp/plugins Plugin system and bundled plugins and removed needs-decision Awaiting maintainer decision before any implementation comp/plugins Plugin system and bundled plugins labels Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles area/streaming Streaming responses: gateway delivery, provider wire comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins comp/tools Tool registry, model_tools, toolsets dependencies Pull requests that update a dependency file P3 Low — cosmetic, nice to have platform/telegram Telegram bot adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants