Telegram
Telegram (Bot API)
Section titled “Telegram (Bot API)”Status: production-ready for bot DMs + groups via grammY. Long polling is the default mode; webhook mode is optional.
Quick setup
Section titled “Quick setup”Run `/newbot`, follow prompts, and save the token.{ channels: { telegram: { enabled: true, botToken: "123:abc", dmPolicy: "pairing", groups: { "*": { requireMention: true } }, }, },}Env fallback: `TELEGRAM_BOT_TOKEN=...` (default account only).coderclaw gatewaycoderclaw pairing list telegramcoderclaw pairing approve telegram <CODE>Pairing codes expire after 1 hour.Telegram side settings
Section titled “Telegram side settings”If the bot must see all group messages, either:
- disable privacy mode via `/setprivacy`, or- make the bot a group admin.
When toggling privacy mode, remove + re-add the bot in each group so Telegram applies the change.Admin bots receive all group messages, which is useful for always-on group behavior.- `/setjoingroups` to allow/deny group adds- `/setprivacy` for group visibility behaviorAccess control and activation
Section titled “Access control and activation”- `pairing` (default)- `allowlist`- `open` (requires `allowFrom` to include `"*"`)- `disabled`
`channels.telegram.allowFrom` accepts numeric Telegram user IDs. `telegram:` / `tg:` prefixes are accepted and normalized.The onboarding wizard accepts `@username` input and resolves it to numeric IDs.If you upgraded and your config contains `@username` allowlist entries, run `coderclaw doctor --fix` to resolve them (best-effort; requires a Telegram bot token).
### Finding your Telegram user ID
Safer (no third-party bot):
1. DM your bot.2. Run `coderclaw logs --follow`.3. Read `from.id`.
Official Bot API method:curl "https://api.telegram.org/bot<bot_token>/getUpdates"Third-party method (less private): `@userinfobot` or `@getidsbot`.1. **Which groups are allowed** (`channels.telegram.groups`) - no `groups` config: all groups allowed - `groups` configured: acts as allowlist (explicit IDs or `"*"`)
2. **Which senders are allowed in groups** (`channels.telegram.groupPolicy`) - `open` - `allowlist` (default) - `disabled`
`groupAllowFrom` is used for group sender filtering. If not set, Telegram falls back to `allowFrom`.`groupAllowFrom` entries must be numeric Telegram user IDs.
Example: allow any member in one specific group:{ channels: { telegram: { groups: { "-1001234567890": { groupPolicy: "open", requireMention: false, }, }, }, },}Mention can come from:
- native `@botusername` mention, or- mention patterns in: - `agents.list[].groupChat.mentionPatterns` - `messages.groupChat.mentionPatterns`
Session-level command toggles:
- `/activation always`- `/activation mention`
These update session state only. Use config for persistence.
Persistent config example:{ channels: { telegram: { groups: { "*": { requireMention: false }, }, }, },}Getting the group chat ID:
- forward a group message to `@userinfobot` / `@getidsbot`- or read `chat.id` from `coderclaw logs --follow`- or inspect Bot API `getUpdates`Runtime behavior
Section titled “Runtime behavior”- Telegram is owned by the gateway process.
- Routing is deterministic: Telegram inbound replies back to Telegram (the model does not pick channels).
- Inbound messages normalize into the shared channel envelope with reply metadata and media placeholders.
- Group sessions are isolated by group ID. Forum topics append
:topic:<threadId>to keep topics isolated. - DM messages can carry
message_thread_id; CoderClaw routes them with thread-aware session keys and preserves thread ID for replies. - Long polling uses grammY runner with per-chat/per-thread sequencing. Overall runner sink concurrency uses
agents.defaults.maxConcurrent. - Telegram Bot API has no read-receipt support (
sendReadReceiptsdoes not apply).
Feature reference
Section titled “Feature reference”Requirement:
- `channels.telegram.streamMode` is not `"off"` (default: `"partial"`)
Modes:
- `off`: no live preview- `partial`: frequent preview updates from partial text- `block`: chunked preview updates using `channels.telegram.draftChunk`
`draftChunk` defaults for `streamMode: "block"`:
- `minChars: 200`- `maxChars: 800`- `breakPreference: "paragraph"`
`maxChars` is clamped by `channels.telegram.textChunkLimit`.
This works in direct chats and groups/topics.
For text-only replies, CoderClaw keeps the same preview message and performs a final edit in place (no second message).
For complex replies (for example media payloads), CoderClaw falls back to normal final delivery and then cleans up the preview message.
`streamMode` is separate from block streaming. When block streaming is explicitly enabled for Telegram, CoderClaw skips the preview stream to avoid double-streaming.
Telegram-only reasoning stream:
- `/reasoning stream` sends reasoning to the live preview while generating- final answer is sent without reasoning text- Markdown-ish text is rendered to Telegram-safe HTML.- Raw model HTML is escaped to reduce Telegram parse failures.- If Telegram rejects parsed HTML, CoderClaw retries as plain text.
Link previews are enabled by default and can be disabled with `channels.telegram.linkPreview: false`.Native command defaults:
- `commands.native: "auto"` enables native commands for Telegram
Add custom command menu entries:{ channels: { telegram: { customCommands: [ { command: "backup", description: "Git backup" }, { command: "generate", description: "Create an image" }, ], }, },}Rules:
- names are normalized (strip leading `/`, lowercase)- valid pattern: `a-z`, `0-9`, `_`, length `1..32`- custom commands cannot override native commands- conflicts/duplicates are skipped and logged
Notes:
- custom commands are menu entries only; they do not auto-implement behavior- plugin/skill commands can still work when typed even if not shown in Telegram menu
If native commands are disabled, built-ins are removed. Custom/plugin commands may still register if configured.
Common setup failure:
- `setMyCommands failed` usually means outbound DNS/HTTPS to `api.telegram.org` is blocked.
### Device pairing commands (`device-pair` plugin)
When the `device-pair` plugin is installed:
1. `/pair` generates setup code2. paste code in iOS app3. `/pair approve` approves latest pending request
More details: [Pairing](/channels/pairing#pair-via-telegram-recommended-for-ios).{ channels: { telegram: { capabilities: { inlineButtons: "allowlist", }, }, },}Per-account override:{ channels: { telegram: { accounts: { main: { capabilities: { inlineButtons: "allowlist", }, }, }, }, },}Scopes:
- `off`- `dm`- `group`- `all`- `allowlist` (default)
Legacy `capabilities: ["inlineButtons"]` maps to `inlineButtons: "all"`.
Message action example:{ action: "send", channel: "telegram", to: "123456789", message: "Choose an option:", buttons: [ [ { text: "Yes", callback_data: "yes" }, { text: "No", callback_data: "no" }, ], [{ text: "Cancel", callback_data: "cancel" }], ],}Callback clicks are passed to the agent as text:`callback_data: <value>`- `sendMessage` (`to`, `content`, optional `mediaUrl`, `replyToMessageId`, `messageThreadId`)- `react` (`chatId`, `messageId`, `emoji`)- `deleteMessage` (`chatId`, `messageId`)- `editMessage` (`chatId`, `messageId`, `content`)
Channel message actions expose ergonomic aliases (`send`, `react`, `delete`, `edit`, `sticker`, `sticker-search`).
Gating controls:
- `channels.telegram.actions.sendMessage`- `channels.telegram.actions.editMessage`- `channels.telegram.actions.deleteMessage`- `channels.telegram.actions.reactions`- `channels.telegram.actions.sticker` (default: disabled)
Reaction removal semantics: [/tools/reactions](/tools/reactions)- `[[reply_to_current]]` replies to the triggering message- `[[reply_to:<id>]]` replies to a specific Telegram message ID
`channels.telegram.replyToMode` controls handling:
- `off` (default)- `first`- `all`
Note: `off` disables implicit reply threading. Explicit `[[reply_to_*]]` tags are still honored.- topic session keys append `:topic:<threadId>`- replies and typing target the topic thread- topic config path: `channels.telegram.groups.<chatId>.topics.<threadId>`
General topic (`threadId=1`) special-case:
- message sends omit `message_thread_id` (Telegram rejects `sendMessage(...thread_id=1)`)- typing actions still include `message_thread_id`
Topic inheritance: topic entries inherit group settings unless overridden (`requireMention`, `allowFrom`, `skills`, `systemPrompt`, `enabled`, `groupPolicy`).
Template context includes:
- `MessageThreadId`- `IsForum`
DM thread behavior:
- private chats with `message_thread_id` keep DM routing but use thread-aware session keys/reply targets.Telegram distinguishes voice notes vs audio files.
- default: audio file behavior- tag `[[audio_as_voice]]` in agent reply to force voice-note send
Message action example:{ action: "send", channel: "telegram", to: "123456789", media: "https://example.com/voice.ogg", asVoice: true,}### Video messages
Telegram distinguishes video files vs video notes.
Message action example:{ action: "send", channel: "telegram", to: "123456789", media: "https://example.com/video.mp4", asVideoNote: true,}Video notes do not support captions; provided message text is sent separately.
### Stickers
Inbound sticker handling:
- static WEBP: downloaded and processed (placeholder `<media:sticker>`)- animated TGS: skipped- video WEBM: skipped
Sticker context fields:
- `Sticker.emoji`- `Sticker.setName`- `Sticker.fileId`- `Sticker.fileUniqueId`- `Sticker.cachedDescription`
Sticker cache file:
- `~/.coderclaw/telegram/sticker-cache.json`
Stickers are described once (when possible) and cached to reduce repeated vision calls.
Enable sticker actions:{ channels: { telegram: { actions: { sticker: true, }, }, },}Send sticker action:{ action: "sticker", channel: "telegram", to: "123456789", fileId: "CAACAgIAAxkBAAI...",}Search cached stickers:{ action: "sticker-search", channel: "telegram", query: "cat waving", limit: 5,}When enabled, CoderClaw enqueues system events like:
- `Telegram reaction added: 👍 by Alice (@alice) on msg 42`
Config:
- `channels.telegram.reactionNotifications`: `off | own | all` (default: `own`)- `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` (default: `minimal`)
Notes:
- `own` means user reactions to bot-sent messages only (best-effort via sent-message cache).- Telegram does not provide thread IDs in reaction updates. - non-forum groups route to group chat session - forum groups route to the group general-topic session (`:topic:1`), not the exact originating topic
`allowed_updates` for polling/webhook include `message_reaction` automatically.Resolution order:
- `channels.telegram.accounts.<accountId>.ackReaction`- `channels.telegram.ackReaction`- `messages.ackReaction`- agent identity emoji fallback (`agents.list[].identity.emoji`, else "👀")
Notes:
- Telegram expects unicode emoji (for example "👀").- Use `""` to disable the reaction for a channel or account.Telegram-triggered writes include:
- group migration events (`migrate_to_chat_id`) to update `channels.telegram.groups`- `/config set` and `/config unset` (requires command enablement)
Disable:{ channels: { telegram: { configWrites: false, }, },}Webhook mode:
- set `channels.telegram.webhookUrl`- set `channels.telegram.webhookSecret` (required when webhook URL is set)- optional `channels.telegram.webhookPath` (default `/telegram-webhook`)- optional `channels.telegram.webhookHost` (default `127.0.0.1`)
Default local listener for webhook mode binds to `127.0.0.1:8787`.
If your public endpoint differs, place a reverse proxy in front and point `webhookUrl` at the public URL.Set `webhookHost` (for example `0.0.0.0`) when you intentionally need external ingress.CLI send target can be numeric chat ID or username:coderclaw message send --channel telegram --target 123456789 --message "hi"coderclaw message send --channel telegram --target @name --message "hi"Troubleshooting
Section titled “Troubleshooting”- If `requireMention=false`, Telegram privacy mode must allow full visibility. - BotFather: `/setprivacy` -> Disable - then remove + re-add bot to group- `coderclaw channels status` warns when config expects unmentioned group messages.- `coderclaw channels status --probe` can check explicit numeric group IDs; wildcard `"*"` cannot be membership-probed.- quick session test: `/activation always`.- when `channels.telegram.groups` exists, group must be listed (or include `"*"`)- verify bot membership in group- review logs: `coderclaw logs --follow` for skip reasons- authorize your sender identity (pairing and/or numeric `allowFrom`)- command authorization still applies even when group policy is `open`- `setMyCommands failed` usually indicates DNS/HTTPS reachability issues to `api.telegram.org`- Node 22+ + custom fetch/proxy can trigger immediate abort behavior if AbortSignal types mismatch.- Some hosts resolve `api.telegram.org` to IPv6 first; broken IPv6 egress can cause intermittent Telegram API failures.- Validate DNS answers:dig +short api.telegram.org Adig +short api.telegram.org AAAAMore help: Channel troubleshooting.
Telegram config reference pointers
Section titled “Telegram config reference pointers”Primary reference:
-
channels.telegram.enabled: enable/disable channel startup. -
channels.telegram.botToken: bot token (BotFather). -
channels.telegram.tokenFile: read token from file path. -
channels.telegram.dmPolicy:pairing | allowlist | open | disabled(default: pairing). -
channels.telegram.allowFrom: DM allowlist (numeric Telegram user IDs).openrequires"*".coderclaw doctor --fixcan resolve legacy@usernameentries to IDs. -
channels.telegram.groupPolicy:open | allowlist | disabled(default: allowlist). -
channels.telegram.groupAllowFrom: group sender allowlist (numeric Telegram user IDs).coderclaw doctor --fixcan resolve legacy@usernameentries to IDs. -
channels.telegram.groups: per-group defaults + allowlist (use"*"for global defaults).channels.telegram.groups.<id>.groupPolicy: per-group override for groupPolicy (open | allowlist | disabled).channels.telegram.groups.<id>.requireMention: mention gating default.channels.telegram.groups.<id>.skills: skill filter (omit = all skills, empty = none).channels.telegram.groups.<id>.allowFrom: per-group sender allowlist override.channels.telegram.groups.<id>.systemPrompt: extra system prompt for the group.channels.telegram.groups.<id>.enabled: disable the group whenfalse.channels.telegram.groups.<id>.topics.<threadId>.*: per-topic overrides (same fields as group).channels.telegram.groups.<id>.topics.<threadId>.groupPolicy: per-topic override for groupPolicy (open | allowlist | disabled).channels.telegram.groups.<id>.topics.<threadId>.requireMention: per-topic mention gating override.
-
channels.telegram.capabilities.inlineButtons:off | dm | group | all | allowlist(default: allowlist). -
channels.telegram.accounts.<account>.capabilities.inlineButtons: per-account override. -
channels.telegram.replyToMode:off | first | all(default:off). -
channels.telegram.textChunkLimit: outbound chunk size (chars). -
channels.telegram.chunkMode:length(default) ornewlineto split on blank lines (paragraph boundaries) before length chunking. -
channels.telegram.linkPreview: toggle link previews for outbound messages (default: true). -
channels.telegram.streamMode:off | partial | block(live stream preview). -
channels.telegram.mediaMaxMb: inbound/outbound media cap (MB). -
channels.telegram.retry: retry policy for outbound Telegram API calls (attempts, minDelayMs, maxDelayMs, jitter). -
channels.telegram.network.autoSelectFamily: override Node autoSelectFamily (true=enable, false=disable). Defaults to disabled on Node 22 to avoid Happy Eyeballs timeouts. -
channels.telegram.proxy: proxy URL for Bot API calls (SOCKS/HTTP). -
channels.telegram.webhookUrl: enable webhook mode (requireschannels.telegram.webhookSecret). -
channels.telegram.webhookSecret: webhook secret (required when webhookUrl is set). -
channels.telegram.webhookPath: local webhook path (default/telegram-webhook). -
channels.telegram.webhookHost: local webhook bind host (default127.0.0.1). -
channels.telegram.actions.reactions: gate Telegram tool reactions. -
channels.telegram.actions.sendMessage: gate Telegram tool message sends. -
channels.telegram.actions.deleteMessage: gate Telegram tool message deletes. -
channels.telegram.actions.sticker: gate Telegram sticker actions — send and search (default: false). -
channels.telegram.reactionNotifications:off | own | all— control which reactions trigger system events (default:ownwhen not set). -
channels.telegram.reactionLevel:off | ack | minimal | extensive— control agent’s reaction capability (default:minimalwhen not set).
Telegram-specific high-signal fields:
- startup/auth:
enabled,botToken,tokenFile,accounts.* - access control:
dmPolicy,allowFrom,groupPolicy,groupAllowFrom,groups,groups.*.topics.* - command/menu:
commands.native,customCommands - threading/replies:
replyToMode - streaming:
streamMode(preview),draftChunk,blockStreaming - formatting/delivery:
textChunkLimit,chunkMode,linkPreview,responsePrefix - media/network:
mediaMaxMb,timeoutSeconds,retry,network.autoSelectFamily,proxy - webhook:
webhookUrl,webhookSecret,webhookPath,webhookHost - actions/capabilities:
capabilities.inlineButtons,actions.sendMessage|editMessage|deleteMessage|reactions|sticker - reactions:
reactionNotifications,reactionLevel - writes/history:
configWrites,historyLimit,dmHistoryLimit,dms.*.historyLimit