NanoClaw

NanoClaw scopes Telegram identity and crashes per instance

NanoClaw patches Telegram sender-scope checks to trust the channel’s own identity, pins update types so channel posts are not blackholed, and scopes crash strikes to the bot instance that earned them.

← Back to homeOriginal source ↗

nanocoai/nanoclaw PR #3450 is the inspected primary source: “Telegram: trust channel's own identity in sender_scope gate.” The related records below were inspected as supporting context rather than independent confirmation.

The facts

- nanocoai/nanoclaw PR #3450 was created 2026-08-22T19:58:37Z and was open at inspection; its title is “Telegram: trust channel's own identity in sender_scope gate.” - nanocoai/nanoclaw PR #3449 was created 2026-08-22T19:43:23Z and was open at inspection; its title is “fix(telegram): pin explicit allowedUpdates to stop channel-post blackholing.” - nanocoai/nanoclaw PR #3447 was created 2026-08-22T19:17:24Z and was open at inspection; its title is “fix(circuit-breaker): scope crash strikes to the instance that earned them.” - The repository reported 30604 stars, 940 open issues, default branch main, and last push 2026-08-22T19:46:14Z. - The latest tagged-release baseline checked was v2.2.0 published 2026-08-13T20:43:27Z.

What changed

nanocoai/nanoclaw PR #3450 reports: Fixes #2991 Problem Telegram broadcast-channel posts are anonymous — the Bot API attributes them to the channel itself via sender_chat (no from). @chat-adapter/telegram's toReactionActorAuthor maps that to an author userId of chat:. That identity is never a member of agent_group_members, so a wiring with sender_scope='known' (the default the channel-registration flow produces) could never engage on any channel post — every message failed the sender-scope gate silently. Fix setSenderScopeGate in src/modules/permissions/index.ts now recognizes the wired channel's own posting identity — userId of the form chat: where matches the messaging group's own platform_id — and treats it as trusted, alongside the existing owner/global-admin/admin-of-group/member checks. This is suggested fix #2 from the issue: the channel posting to itself is not an unknown third party, so it shouldn't be gated by sender_scope='known'. Scoped to channel_type === 'telegram' and the chat: prefix convention, which is specific to @chat-adapter/telegram's anonymous-sender_chat/reaction-actor mapping — no other adapter uses this identity shape. Test plan Added src/modules/permissions/telegram-channel-sender-scope.test.ts: a sender_scope='known' wiring now engages on an anonymous channel post whose sender_chat id matches the wired channel a post claiming a different chat id is still correctly refused Verified against the pre-fix code that the first test fails without the change (reproduces #2991) and passes with it npx vitest run src/modules/permissions src/router.test.ts — 51/51 passed npx tsc --noEmit — no new errors

nanocoai/nanoclaw PR #3449 reports: Summary Telegram persists allowed_updates **server-side, per bot token** — per the Bot API docs, omitting the parameter on getUpdates means "the previous setting will be used." The Telegram adapter here starts polling with no explicit longPolling.allowedUpdates, so any bot token that was ever polled elsewhere with a narrower list (e.g. NanoClaw v1, or any other grammY/telegraf setup with an explicit list) permanently filters out update types outside that stale set — most visibly channel_post. Broadcast channels then appear completely dead: no log line, no dropped-message row, nothing to debug from, while DMs and groups work fine. This bites every v1→v2 migration that reuses the same bot token. Fixes #2989 Fix Always pass an explicit longPolling.allowedUpdates list on the polling adapter so the server-side filter can never leak in unnoticed: This restores parity with Telegram's own default allowed_updates set (everything except chat_member, message_reaction, message_reaction_count), so the base behavior is unchanged for tokens that were never narrowed — it only guarantees the list is always sent, so a previously-narrowed token can't keep filtering silently. Relationship to #2544 2544 (open, same base branch) also touches this exact config to add callback_query and message_reaction for a different reason (unlocking reactions/inline keyboards), but its list — ['message', 'edited_message', 'callback_query', 'message_reaction'] — omits channel_post and edited_channel_post, so it does not fix this issue; broadcast channels would

nanocoai/nanoclaw PR #3447 reports: The failure mode The startup circuit breaker counts consecutive crashes in data/circuit-breaker.json and delays the next start on a schedule (0s, 0s, 10s, 30s, 2min, 5min, 15min cap). The counter is keyed to nothing but the file's existence, so it silently belongs to whoever mounts data/ next. That's fine when data/ and the host live and die together. It breaks whenever the directory outlives the process that wrote it: a working tree on a durable volume mounted into successive fresh containers or pods, a data/ restored from backup or copied to a new machine, any deployment where the tree is the durable thing and the host instance is disposable. In those setups a brand-new, healthy instance reads the strikes of the crashy instance that came before it and starts life mid-backoff — up to a 15 minute delay it never earned, compounding on each start. The only cure was deleting the file by hand, which is not something an operator has any reason to know to do. The fix State now records os.hostname() alongside the attempt count. On startup, state whose host doesn't match this instance is treated the same as no state at all: start clean at attempt 1, and take ownership on the write. os.hostname() is the right boundary because it is stable exactly as long as the

Why it matters

Multiple adapters on one host require instance-local identity and failure budgets. A global sender assumption or crash counter can silence the wrong bot. The operator test is whether the system remains bounded and its receipts still describe the action after failure, retry or restart.

Current

The primary record was open when captured on 2026-08-23. The tagged-release baseline was v2.2.0 published 2026-08-13T20:43:27Z. Repository metadata, full source bodies, current pull requests and issues, releases, Google News, Hacker News, Lobsters, Metamesh and the rendered ClawCharts row were inspected. ClawCharts selected the subject; it did not prove the claim.

Evidence

The primary URL is https://github.com/nanocoai/nanoclaw/pull/3450. Supporting records are https://github.com/nanocoai/nanoclaw/pull/3449 and https://github.com/nanocoai/nanoclaw/pull/3447. Source bodies, timestamps and states are preserved in the daily evidence bundle. Test counts and reproductions remain attributed to their authors unless identified as independently rerun.

Source boundary

Open work is described as open, closed work as closed, and operator reports as reports. A pull request is evidence of proposed or reviewed direction, not proof of a shipped release. Search residue, package mirrors and historically published source spines were excluded.

Operator take

Bind sender policy, polling updates and circuit-breaker strikes to the adapter instance, then verify that one bot’s traffic or crash cannot disable another. Preserve a before-state receipt, make the smallest reversible change, and verify the original failure independently.

Caveat

Public project records are mutable. Status, scope and evidence can change after publication. This brief records the inspected state and does not authorize changes to a reader’s deployment.

Source inspected; source state, environment and release boundary remain explicit.