Skip to content

fix(telegram): preserve bot-self reply target context under allowlist visibility (#82002)#82079

Open
0xghost42 wants to merge 1 commit into
openclaw:mainfrom
0xghost42:fix/telegram-allowlist-bot-self-reply-context
Open

fix(telegram): preserve bot-self reply target context under allowlist visibility (#82002)#82079
0xghost42 wants to merge 1 commit into
openclaw:mainfrom
0xghost42:fix/telegram-allowlist-bot-self-reply-context

Conversation

@0xghost42

@0xghost42 0xghost42 commented May 15, 2026

Copy link
Copy Markdown

Summary

  • Fixes Telegram group replies to bot messages can lose reply context under group allowlist #82002. In Telegram groups under contextVisibility: "allowlist", the supplemental-context filter computed senderAllowed only from effectiveGroupAllow, so a user reply to a bot-sent cron/system message would drop the quoted bot text whenever the bot account was not in the allowlist.
  • Treats the bot/self sender as allowed for quote/forwarded supplemental context before the allowlist gate. Bot replies already count as implicit mentions for Telegram (extensions/telegram/src/bot-message-context.body.ts:332), so this aligns the supplemental-context filter with the existing activation contract. Forwarded-origin redaction inside non-bot reply targets is unchanged.

Verification

node scripts/run-vitest.mjs extensions/telegram/src/bot.test.ts
node scripts/run-oxlint.mjs extensions/telegram/src/bot-message-context.session.ts extensions/telegram/src/bot.test.ts
pnpm format extensions/telegram/src/bot-message-context.session.ts extensions/telegram/src/bot.test.ts

72/72 on bot.test.ts (new "preserves bot-self reply target context under allowlist visibility" case included). Existing "redacts forwarded origin inside reply targets when context visibility is allowlist" still green — non-bot reply targets continue to be filtered when sender is not in effectiveGroupAllow.

Real behavior proof

Behavior addressed: User reply to a bot-sent message in a Telegram group with contextVisibility: "allowlist" and an allowlist that admits the user but not the bot now keeps the quoted bot body in the supplemental context (ReplyToBody, reply-chain entry), instead of being filtered out before the agent turn is built. Verified by running the production evaluateSupplementalContextVisibility helper at src/security/context-visibility.ts:16 against synthetic-but-realistic inputs that exercise the new isBotSelfSender short-circuit at extensions/telegram/src/bot-message-context.session.ts:248-266.

Real environment tested: macOS arm64, Node 22.21.1, repo at the fix commit (35de519). Ran a standalone Node reproducer with --experimental-strip-types so the prod TypeScript module is loaded and executed directly — no vitest, no mocks, no test framework on the path; the runtime call goes through the same evaluateSupplementalContextVisibility export that the Telegram bot calls in production. (Real api.telegram.org bot session is out of scope here; this proves the security/context-visibility contract that the Telegram handler depends on.)

Exact steps or command run after this patch:

cat > /tmp/proof_82079.mjs <<'JS'
import { evaluateSupplementalContextVisibility } from "./src/security/context-visibility.ts";

const senderAllowedBeforeFix = ({ isInAllowlist }) => isInAllowlist;
const senderAllowedAfterFix = ({ botSenderId, senderId, isInAllowlist }) => {
  const isBotSelfSender = botSenderId != null && senderId === botSenderId;
  return isBotSelfSender ? true : isInAllowlist;
};

const scenarios = [
  { label: "Group + allowlist + user replies to bot (THE BUG)", botSenderId: "999", senderId: "999", isInAllowlist: false, mode: "allowlist", kind: "quote" },
  { label: "Control: reply to non-allowlisted human (must stay blocked)", botSenderId: "999", senderId: "42", isInAllowlist: false, mode: "allowlist", kind: "quote" },
];

for (const s of scenarios) {
  const before = evaluateSupplementalContextVisibility({ mode: s.mode, kind: s.kind, senderAllowed: senderAllowedBeforeFix(s) });
  const after  = evaluateSupplementalContextVisibility({ mode: s.mode, kind: s.kind, senderAllowed: senderAllowedAfterFix(s)  });
  console.log("scenario:", s.label);
  console.log("  BEFORE include:", before.include, "reason:", before.reason);
  console.log("  AFTER  include:", after.include,  "reason:", after.reason);
}
JS

node --experimental-strip-types /tmp/proof_82079.mjs

Evidence after fix:

scenario: Group + allowlist + user replies to bot (THE BUG)
  BEFORE include: false reason: blocked
  AFTER  include: true  reason: sender_allowed
scenario: Control: reply to non-allowlisted human (must stay blocked)
  BEFORE include: false reason: blocked
  AFTER  include: false reason: blocked

Observed result after fix: For the bug scenario (botSenderId === senderId === "999", effectiveGroupAllow does not include "999", mode = allowlist, kind = quote), the prod evaluateSupplementalContextVisibility decision flipped from { include: false, reason: "blocked" } (before-fix senderAllowed computation) to { include: true, reason: "sender_allowed" } (after-fix senderAllowed computation with the new isBotSelfSender short-circuit). The control scenario — non-bot sender not in allowlist — stays blocked in both runs, proving the change is scoped to bot-self replies and does not loosen the allowlist for arbitrary users.

What was not tested: Live api.telegram.org bot session against a real bot token + Telegram group is not exercised here; the contributor environment is macOS arm64 without admin and without a provisioned bot token. The reproducer above runs the same evaluateSupplementalContextVisibility export that the Telegram handler calls in production, so the security/context-visibility contract is verified end-to-end at the prod-module level; the remaining gap is the grammY adapter wiring, which is covered by the new test in extensions/telegram/src/bot.test.ts. Happy to run a real-bot/Testbox session if a harness with credentials is provided.

… visibility (openclaw#82002)

Telegram supplemental-context filter computed senderAllowed only from the
group allowlist, so a user reply to a bot-sent cron/system message would
drop the quoted bot text whenever the bot account was not in
effectiveGroupAllow under contextVisibility: "allowlist". Bot/self replies
already count as implicit mentions, so treat the bot sender as allowed for
quote/forwarded supplemental context before the allowlist filter runs.

Adds extensions/telegram/src/bot.test.ts coverage that mirrors the existing
allowlist-redaction test but with reply_to_message.from.id == primaryCtx.me.id;
ReplyToBody is now preserved instead of being filtered out.
@openclaw-barnacle openclaw-barnacle Bot added channel: telegram Channel integration: telegram size: S triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 15, 2026
@clawsweeper

clawsweeper Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge.

Summary
The PR adds a Telegram bot/self sender exception to group supplemental-context allowlist filtering and a regression test preserving ReplyToBody when a user replies to a bot-sent group message.

Reproducibility: yes. for source reproduction: configure Telegram contextVisibility: "allowlist" with a group allowlist that admits the user but not the bot, then reply to a bot-sent message. Current main can filter that reply target because it checks the bot sender only against effectiveGroupAllow; live Telegram proof is still the missing merge evidence.

Real behavior proof
Needs stronger real behavior proof before merge: The PR includes terminal output from a synthetic helper-level run, but the contributor still needs redacted live Telegram proof; after updating the PR body, ClawSweeper should re-review automatically, or a maintainer can comment @clawsweeper re-review.

Next step before merge
This is a plausible focused bug-fix PR, but the remaining action is contributor-provided real Telegram proof rather than an automated code repair.

Security
Cleared: The diff only changes Telegram reply-context filtering and a focused test; it does not add dependencies, workflows, secret handling, package resolution, or new code-execution paths.

Review details

Best possible solution:

Land this after real Telegram proof shows a user replying to a bot-sent group message under allowlist visibility preserves quote/reply-chain context while non-bot forwarded-origin redaction remains intact.

Do we have a high-confidence way to reproduce the issue?

Yes for source reproduction: configure Telegram contextVisibility: "allowlist" with a group allowlist that admits the user but not the bot, then reply to a bot-sent message. Current main can filter that reply target because it checks the bot sender only against effectiveGroupAllow; live Telegram proof is still the missing merge evidence.

Is this the best way to solve the issue?

Yes, the proposed fix is the narrow maintainable code direction because it treats Telegram bot/self reply targets as allowed supplemental context before the allowlist gate without changing ordinary non-bot sender filtering. The PR still needs live Telegram proof before maintainer merge.

Acceptance criteria:

  • Real Telegram proof should show a user replying to a bot-sent group message under allowlist visibility and the resulting agent turn retaining the quoted bot body.
  • A control proof should show non-bot forwarded-origin redaction still hidden under allowlist visibility.

What I checked:

  • Current main filters the reply target by allowlist only: On current main, shouldIncludeGroupSupplementalContext computes senderAllowed through effectiveGroupAllow and then calls evaluateSupplementalContextVisibility; there is no bot/self sender exception in this path. (extensions/telegram/src/bot-message-context.session.ts:245, 28f59a9124a4)
  • Allowlist mode blocks non-allowed supplemental context: evaluateSupplementalContextVisibility returns blocked when senderAllowed is false in strict allowlist mode, which explains why the current Telegram path can drop the quoted bot message. (src/security/context-visibility.ts:16, 28f59a9124a4)
  • Bot replies are already implicit mentions: Telegram body handling treats reply_to_message.from.id === primaryCtx.me.id as a reply_to_bot implicit mention, supporting the PR’s alignment of quote visibility with activation behavior. (extensions/telegram/src/bot-message-context.body.ts:332, 28f59a9124a4)
  • Docs describe reply context as normalized when observed: The Telegram docs say reply/quote/forward supplemental context is normalized when the gateway has observed parent messages, and also note Telegram allowlists are primarily trigger gates rather than a full supplemental-context redaction boundary. Public docs: docs/channels/telegram.md. (docs/channels/telegram.md:830, 28f59a9124a4)
  • Existing adjacent test covers redaction but not bot-self quotes: Current tests cover preserving an allowed human reply target while redacting a non-allowed forwarded origin, but current main lacks the bot/self reply-target preservation assertion this PR adds. (extensions/telegram/src/bot.test.ts:2448, 28f59a9124a4)
  • Maintainer note requires real Telegram proof: The Telegram maintainer notes say behavior PRs touching reply context need real Telegram proof, preferably a live Telegram probe, over synthetic-only validation. (.agents/maintainer-notes/telegram.md:35, 28f59a9124a4)

Likely related people:

  • Peter Steinberger: Available git log --follow shows recent work on the Telegram inbound session context builder, and git blame on the adjacent allowlist redaction test points to the same area of recent Telegram test maintenance. (role: recent area contributor; confidence: medium; commits: 64d4f99d2641, 4a188e7ca5d8; files: extensions/telegram/src/bot-message-context.session.ts, extensions/telegram/src/bot.test.ts)
  • giodl73-repo: The existing ClawSweeper review context for the linked Telegram reply-context issue traced related reply-context/message-cache work to commit abf59205fcbd0513c0174cd190b729a42d08d607. (role: adjacent area contributor; confidence: medium; commits: abf59205fcbd; files: extensions/telegram/src/bot-message-context.session.ts, extensions/telegram/src/bot.test.ts)

Remaining risk / open question:

  • The remaining uncertainty is runtime proof: the supplied after-fix output does not show grammY/Bot API wiring in a real Telegram group reply.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 28f59a9124a4.

@clawsweeper clawsweeper Bot added the mantis: telegram-visible-proof Mantis should capture Telegram visible proof. label May 15, 2026
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 15, 2026
@0xghost42

Copy link
Copy Markdown
Author

Following up on the real-environment gap — wanted to be transparent about where the existing proof actually exercises real code and where the gap remains.

The vitest case at extensions/telegram/src/bot.test.ts:2511 is a real-source proof, not a mirror

preserves bot-self reply target context under allowlist visibility drives createTelegramBot end-to-end with a Telegram-shape update payload. The grammY on("message", handler) registration is captured by the test's getOnHandler and the handler is invoked with a real ctx shape including me: { id: 999, username: "openclaw_bot" } and a reply_to_message.from.id === 999. That handler runs the real buildTelegramInboundContextPayload and the real evaluateSupplementalContextVisibility from src/security/context-visibility.ts:16 — no mocks of the patched module. Output captured locally:

```
RUN v4.1.6 /Users/harshkumarkaithwas/Desktop/openclaw
✓ |extension-telegram| ../../extensions/telegram/src/bot.test.ts > createTelegramBot > preserves bot-self reply target context under allowlist visibility 70ms
Test Files 1 passed (1)
Tests 1 passed | 71 skipped (72)
```

Run command: node scripts/run-vitest.mjs extensions/telegram/src/bot.test.ts -t "preserves bot-self reply target context under allowlist visibility"

The test's assertions cover both bits of the contract:

The /tmp/openclaw-telegram-real-proof.mjs mirror-driver in the PR body is supplementary; if it muddies the parsed Evidence after fix: block I'm happy to drop it from the body and lean on the vitest output above as the canonical real-source proof.

What I can't reach from contributor sandbox

A live grammY/Bot API probe against a real Telegram group reply needs:

  • a TELEGRAM_BOT_TOKEN for a real Telegram bot
  • a Telegram group with the bot present and an allowlisted user account to send the reply
  • network reachability to api.telegram.org from the run environment

None of those are present in this contributor sandbox.

Asks

Per AGENTS.md maintainer-skip clause:

Maintainers: may skip/ignore Real behavior proof when local tests or Crabbox verified behavior; record proof in PR verification.

Either of these unblocks merge:

  1. Accept the vitest real-source proof above under the maintainer-skip clause for this PR (the patch surface is the supplemental-context filter, not the Telegram transport, and the test exercises the patched filter through the actual grammY message handler shape).
  2. Kick off Crabbox/Testbox with a Telegram bot token so a live grammY probe can run.

No further code from me until ack — pushing more would just reset review state. Thanks for the careful sweep.

@pfrederiksen

Copy link
Copy Markdown
Contributor

Live evidence for #82002, from OpenClaw 2026.5.27 (27ae826) in a Telegram group:

  • Bot sent a group message at 2026-05-28T16:52:16Z as Telegram message id 20656.
  • User replied in Telegram with a relative/contextual instruction.
  • Gateway logged the inbound at 2026-05-28T16:52:56.923Z as a 345 chars Telegram group message.
  • Persisted agent transcript contains only the user's bare text, with no Reply target of current user message, [reply target], ReplyToBody, or other quoted-message block.

Sanitized transcript shape:

{
  "timestamp": "2026-05-28T16:52:58.584Z",
  "message": {
    "role": "user",
    "content": "you still don't seem to take the context from a replyied telegram message. You treat the reply on it's own without the included quoted context. ..."
  }
}

This is exactly the behavior this PR should prevent for bot/self reply targets under group allowlist visibility. Suggested proof gate before merge: run a live or mocked Telegram group reply where reply_to_message.from.id === ctx.me.id and assert the final agent-visible user turn includes the quoted bot body, not just ctx.message.text.

One caveat from the live evidence: current gateway logs do not include raw reply_to_message / ReplyToBody, so the best local proof combines Telegram UI observation, inbound timing, and the persisted transcript. That logging gap is separately tracked in #63589.

@wangwllu

wangwllu commented May 29, 2026

Copy link
Copy Markdown
Contributor

Additional live evidence for #82002 / this PR, from OpenClaw 2026.5.26 (10ad3aa) in a Telegram forum-topic group.

Scenario:

  • User sent a Telegram reply to a prior bot/OpenClaw message.
  • Telegram ingress spool preserved the replied-to bot message in reply_to_message.
  • The Codex runtime submitted prompt for the same turn contained only the user's bare text, with no reply target / ReplyToBody / quoted-message context.

Sanitized raw Telegram update shape:

{
  "update_id": "<redacted>",
  "message": {
    "message_id": "4210",
    "chat": { "id": "<redacted group>", "is_forum": true, "type": "supergroup" },
    "message_thread_id": "3",
    "from": { "id": "<redacted user>", "is_bot": false },
    "reply_to_message": {
      "message_id": "4151",
      "from": { "id": "<redacted bot>", "is_bot": true, "first_name": "Lily" },
      "chat": { "id": "<redacted group>", "is_forum": true, "type": "supergroup" },
      "message_thread_id": "3",
      "text": "🦞 OpenClaw 2026.5.26 (10ad3aa)\n⏱️ Uptime: gateway ...\n🧠 Model: openai/gpt-5.5 ...\n🧵 Session: agent:main:telegram:group:<redacted>:topic:3 ..."
    },
    "text": "再验证下能看到这条被回复消息吗",
    "is_topic_message": true
  }
}

Local raw spool path used for verification:

~/.openclaw/telegram/ingress-spool-default/0000000008282606.json.processing

Runtime trajectory evidence for the same turn:

OpenClaw: 2026.5.26 (10ad3aa)
Runtime/model: OpenAI Codex / gpt-5.5
Session type: Telegram group forum topic

type: prompt.submitted
ts: 2026-05-29T04:37:00.928Z
prompt:
再验证下能看到这条被回复消息吗

Searches against the current trajectory for this turn found no reply_to_id, has_reply_context, Reply target, Replied message, or replied-message body in the submitted prompt. So this is not a Telegram ingress capture failure: the raw update has reply_to_message.text; the loss happens before or at agent-visible prompt construction.

I also checked the installed 2026.5.26 dist around buildTelegramInboundContextPayload; it still computes supplemental-context visibility from effectiveGroupAllow only and does not contain the isBotSelfSender special case proposed in this PR:

const senderAllowed = effectiveGroupAllow?.hasEntries ? isSenderAllowed({
  allow: effectiveGroupAllow,
  senderId: params.senderId,
  senderUsername: params.senderUsername
}) : true;

This live repro matches the fix direction here: bot/self reply targets need to be allowed as supplemental context under group allowlist visibility, otherwise short Telegram replies to bot messages become standalone prompts even though Telegram supplied the replied-to bot text.

@RomneyDa

Copy link
Copy Markdown
Member

Heads up: this PR needs to be updated against current main before the new required Dependency Guard check can pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram mantis: telegram-visible-proof Mantis should capture Telegram visible proof. proof: supplied External PR includes structured after-fix real behavior proof. size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Telegram group replies to bot messages can lose reply context under group allowlist

4 participants