thedotmack/claude-mem · warning · Error
Telegram API responded ${status} ${statusText}
Error message
Telegram API responded ${status} ${statusText} What it means
postOne() sends a single sendMessage request to the Telegram Bot API; if the response is not ok it throws an Error with the HTTP status and statusText. The caller notifyTelegram wraps each observation's send in its own try/catch and logs a warning, so one bad send does not abort the batch — but the message for that observation is lost.
Source
Thrown at src/services/integrations/TelegramNotifier.ts:63
const idEscaped = escapeMarkdownV2(String(observationId));
return `${emoji} *${type}* — ${title}\n${subtitle}\nProject: \`${projectEscaped}\` · obs \\#${idEscaped}`;
}
async function postOne(botToken: string, chatId: string, text: string): Promise<void> {
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
const response = await fetch(url, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
chat_id: chatId,
text,
parse_mode: 'MarkdownV2',
}),
});
if (!response.ok) {
const status = response.status;
const statusText = response.statusText;
throw new Error(`Telegram API responded ${status} ${statusText}`);
}
}
export async function notifyTelegram(input: TelegramNotifyInput): Promise<void> {
const settings = SettingsDefaultsManager.loadFromFile(USER_SETTINGS_PATH);
if (settings.CLAUDE_MEM_TELEGRAM_ENABLED !== 'true') {
return;
}
const botToken = settings.CLAUDE_MEM_TELEGRAM_BOT_TOKEN;
const chatId = settings.CLAUDE_MEM_TELEGRAM_CHAT_ID;
if (!botToken || !chatId) {
return;
}
const triggerTypes = splitCsv(settings.CLAUDE_MEM_TELEGRAM_TRIGGER_TYPES);
const triggerConcepts = splitCsv(settings.CLAUDE_MEM_TELEGRAM_TRIGGER_CONCEPTS);View on GitHub (pinned to d768ba3643)
Solutions
- Verify the bot token: curl https://api.telegram.org/bot<TOKEN>/getMe — a 401 means the token is wrong/revoked.
- Confirm chat_id by sending /start to the bot and checking getUpdates; ensure the chat_id in settings matches.
- If MarkdownV2 parsing fails, simplify the message or switch parse_mode; ensure escapeMarkdownV2 is applied to all dynamic text.
- For 429, reduce notification volume or add backoff between sends.
Example fix
# before
curl https://api.telegram.org/bot$TOKEN/sendMessage -d chat_id=$CHAT -d text=hi
# -> {"ok":false,"error_code":401,"description":"Unauthorized"}
# after — regenerate token via @BotFather, set it
export CLAUDE_MEM_TELEGRAM_BOT_TOKEN='<new-token>' Defensive patterns
Strategy: try-catch
Validate before calling
// Validate token/chatId shape before sending.
function telegramConfigValid(token?: string, chatId?: string): boolean {
return /^\d+:\w{30,}$/.test(token ?? '') && /^-?\d+$/.test(chatId ?? '');
} Type guard
function isTelegramApiError(e: unknown): boolean {
return e instanceof Error && /Telegram API responded \d+/.test(e.message);
} Try / catch
// notifyTelegram already wraps each send; mirror that pattern when calling directly.
try {
await postOne(botToken, chatId, text);
} catch (e) {
if (e instanceof Error && /Telegram API responded/.test(e.message)) {
logger.warn('TELEGRAM', 'Telegram send failed; non-fatal', {}, e);
return; // one failed message must not break the batch
}
throw e;
} Prevention
- Verify the bot token with getMe during configuration.
- Have the target chat /start the bot so the chat_id is valid and reachable.
- Treat notification failures as non-fatal (the existing loop already logs-and-continues).
- Escape all dynamic text for MarkdownV2 parse_mode.
When it happens
Trigger: Telegram returns non-2xx for a sendMessage call: 401 unauthorized (bad bot token), 400 bad request (chat_id unknown, bot can't message the chat, MarkdownV2 parse error), 403 forbidden (chat blocked the bot), 429 rate limited.
Common situations: CLAUDE_MEM_TELEGRAM_BOT_TOKEN is wrong or revoked; CLAUDE_MEM_TELEGRAM_CHAT_ID is wrong or the user never started /start with the bot; message text contains unescaped MarkdownV2 special chars (the formatter escapes most, but user content can still trip it); hitting Telegram rate limits during a burst of observations.
Related errors
- Failed to get processing status: ${res.status}
- Failed to trigger processing: ${res.status}
- Invalid CLAUDE_MEM_WORKER_PORT in settings.json: missing
- Invalid CLAUDE_MEM_WORKER_PORT in settings.json: ${rawPort}
- Failed to search: ${searchResponse.status} ${searchResponse.
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/546935d84239af6f.
Report an issue: GitHub.