M3: Routing bootstrap and rejection visibility#6212
Conversation
…d rejection visibility Co-Authored-By: Claude <noreply@anthropic.com>
| const rejectionNoticeTimestamps = new Map<string, number>(); | ||
|
|
||
| function shouldSendRejectionNotice(chatId: string): boolean { | ||
| const now = Date.now(); | ||
| const lastSent = rejectionNoticeTimestamps.get(chatId); | ||
| if (lastSent !== undefined && now - lastSent < REJECTION_NOTICE_COOLDOWN_MS) { | ||
| return false; | ||
| } | ||
| rejectionNoticeTimestamps.set(chatId, now); | ||
| return true; |
There was a problem hiding this comment.
🟡 Unbounded memory growth in rejectionNoticeTimestamps map — no eviction of expired entries
The rejectionNoticeTimestamps module-level Map<string, number> grows without bound. Every unique chat ID that triggers a routing rejection adds an entry, but entries are never removed — even after the 5-minute cooldown expires.
Root Cause and Impact
Unlike the DedupCache (at gateway/src/dedup-cache.ts:18-23) which has a maxSize cap and TTL-based eviction, the rejectionNoticeTimestamps map has no eviction mechanism. The shouldSendRejectionNotice function only reads and writes entries but never deletes stale ones:
const rejectionNoticeTimestamps = new Map<string, number>();
function shouldSendRejectionNotice(chatId: string): boolean {
const now = Date.now();
const lastSent = rejectionNoticeTimestamps.get(chatId);
if (lastSent !== undefined && now - lastSent < REJECTION_NOTICE_COOLDOWN_MS) {
return false;
}
rejectionNoticeTimestamps.set(chatId, now);
return true;
}In a scenario where the gateway is misconfigured (routing rejects all messages) and the bot is added to many Telegram groups or receives messages from many unique users, each distinct chatId permanently consumes memory. Over a long-running gateway lifetime, this leads to unbounded memory growth proportional to the total number of unique rejected chat IDs ever seen.
Impact: Slow memory leak in long-running gateway processes. Severity depends on traffic volume — a bot in many groups with misconfigured routing could accumulate thousands of entries that are never cleaned up.
Prompt for agents
In gateway/src/http/routes/telegram-webhook.ts, the rejectionNoticeTimestamps Map (line 32) grows without bound. Add periodic eviction of entries older than REJECTION_NOTICE_COOLDOWN_MS. Two approaches:
1. (Simple) In shouldSendRejectionNotice (lines 34-41), after the cooldown check, periodically sweep the map to delete entries where (now - timestamp) >= REJECTION_NOTICE_COOLDOWN_MS. For example, run the sweep every N calls or every M minutes using a counter or last-sweep timestamp.
2. (Better) Add a maxSize cap similar to DedupCache. When the map exceeds the cap, evict all expired entries. If still over cap, delete the oldest entries. This bounds memory usage regardless of traffic patterns.
Either approach prevents unbounded memory growth from accumulating stale chat ID entries.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bd30fbd007
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const lastSent = rejectionNoticeTimestamps.get(chatId); | ||
| if (lastSent !== undefined && now - lastSent < REJECTION_NOTICE_COOLDOWN_MS) { | ||
| return false; | ||
| } | ||
| rejectionNoticeTimestamps.set(chatId, now); |
There was a problem hiding this comment.
Bound rejection notice cache growth
rejectionNoticeTimestamps is updated for every new chat ID but never evicted, so a long-lived gateway handling many distinct Telegram chats will retain stale IDs forever and grow memory usage over time. This is especially likely for public bots/groups where unique chat IDs can accumulate continuously; adding TTL-based cleanup or a max-size eviction policy would prevent unbounded growth.
Useful? React with 👍 / 👎.
* fix: remove assistantId dependency from Telegram attachment delivery (#6210) Co-authored-by: Claude <noreply@anthropic.com> * feat: add Telegram webhook lifecycle reconciliation (#6211) Co-authored-by: Claude <noreply@anthropic.com> * feat: auto-configure gateway routing for single-assistant mode and add rejection visibility (#6212) Co-authored-by: Claude <noreply@anthropic.com> * feat: add Telegram Bot messaging provider for proactive outbound sends (#6222) Co-authored-by: Claude <noreply@anthropic.com> * feat: harden /deliver/telegram auth and align docs with Telegram capabilities (#6238) Co-authored-by: Claude <noreply@anthropic.com> * fix: correct misleading comment in Telegram attachment download path (#6241) Co-authored-by: Claude <noreply@anthropic.com> * fix: bound rejection notice cache with periodic eviction (#6242) Co-authored-by: Claude <noreply@anthropic.com> * fix: support tokenless providers in withProviderToken and fix testConnection error handling (#6244) Co-authored-by: Claude <noreply@anthropic.com> * fix: always reconcile webhook and normalize ingress URL (#6245) Co-authored-by: Claude <noreply@anthropic.com> * fix: resolve gateway lint error and credential security allowlist for Telegram adapter (#6257) Co-authored-by: Claude <noreply@anthropic.com> * fix: require webhook_secret in Telegram isConnected check (#6259) Co-authored-by: Claude <noreply@anthropic.com> * fix: only default routing policy in single-assistant deployments (#6261) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Auto-configure GATEWAY_UNMAPPED_POLICY=default and GATEWAY_DEFAULT_ASSISTANT_ID in local gateway startup for single-assistant deployments. Add operator-visible Telegram notice and logging when routing is rejected instead of silent drop. Part of #6200.