fix: raw fetch polling loop - bypasses Grammy polling, handles 409 gracefully

- Replaced Grammy bot.start() with manual fetch-based getUpdates loop
- Grammy used only for bot.handleUpdate() (middleware/handlers)
- 409 conflicts handled with 10s backoff + continue (no crash)
- Network errors handled with 3s retry
- Root cause of persistent 409: zombie bun process (PID 72407) from 3:23 AM
  was holding Telegram long-poll lock all day
This commit is contained in:
Azreen Jamal
2026-03-06 21:41:51 +08:00
parent ccfa969261
commit a86865ee25
3 changed files with 51 additions and 54 deletions
-1
View File
@@ -1 +0,0 @@
❌ Poll error: Network request for 'getUpdates' failed!
+2 -44
View File
@@ -1,44 +1,2 @@
🚀 Bot starting... 🤖 CharityRight DevOps Bot starting...
✅ Bot is live — polling started (model: claude-sonnet-4-6) ✅ Bot online — polling for messages
🤖 [775071081] Calling Pi (continue session)...
📤 [775071081] 263 chars in 4.2s
🤖 [775071081] Calling Pi (continue session)...
🔧 [775071081] cat /private/tmp/.env 2>/dev/null || cat ~/.env 2>/dev/null || echo "NO ENV FOUN...
🔧 [775071081] ssh -o ConnectTimeout=5 -p 22 root@159.195.60.33 "
echo '=== UPTIME ===' && upti...
🔧 [775071081] ssh -o ConnectTimeout=5 -p 22 root@159.195.60.33 "
echo '=== HAPROXY ===' && ser...
🔧 [775071081] ssh -o ConnectTimeout=5 -p 22 root@159.195.60.33 "
echo '=== HAPROXY PROCESS ===...
🔧 [775071081] ssh -o ConnectTimeout=5 -p 22 root@159.195.60.33 "cat ~/.env 2>/dev/null || ls /...
🔧 [775071081] ssh -o ConnectTimeout=5 -p 22 root@159.195.60.33 "
echo '=== HIGH LOAD CAUSE ===...
🔧 [775071081] ssh -o ConnectTimeout=5 -p 22 root@159.195.60.33 "grep -r 'ASANA' /root/ /opt/ -...
🔧 [775071081] find /private/tmp -name "*.env" 2>/dev/null; find ~ -maxdepth 3 -name ".env" 2>/...
🔧 [775071081] grep -i 'ASANA' /Users/azreenjamal/pi-vs-claude-code/.env /Users/azreenjamal/pi-...
🔧 [775071081] grep -i 'ASANA' /Users/azreenjamal/pi-vs-claude-code/.env
🔧 [775071081] grep -i 'ASANA\|WORKSPACE\|PROJECT' /Users/azreenjamal/pi-vs-claude-code/.env
🔧 [775071081] # Get workspace and project info from Asana
curl -s -H "Authorization: Bearer 2/...
🔧 [775071081] # Get projects in workspace
curl -s -H "Authorization: Bearer 2/1212300623824347...
🔧 [775071081] # Task 1: qc-server-new FROZEN
curl -s -X POST "https://app.asana.com/api/1.0/ta...
🔧 [775071081] # Task 2: High CPU load + rsync snapshot
curl -s -X POST "https://app.asana.com/...
📤 [775071081] 1026 chars in 190.5s
🤖 [775071081] Calling Pi (continue session)...
🔧 [775071081] ssh -o ConnectTimeout=5 -p 22 root@159.195.60.33 "incus unfreeze qc-server-new &...
🔧 [775071081] ssh -o ConnectTimeout=5 -p 22 root@159.195.60.33 "
incus list qc-server-new --fo...
🔧 [775071081] curl -s -X PUT "https://app.asana.com/api/1.0/tasks/1213564390289259" \
-H "Au...
📤 [775071081] 221 chars in 33.6s
🤖 [775071081] Calling Pi (continue session)...
🔧 [775071081] ssh -o ConnectTimeout=5 -p 22 root@159.195.60.33 "
echo '=== TOP SWAP USERS ==='...
🔧 [775071081] ssh -o ConnectTimeout=5 -p 22 root@159.195.60.33 "
cat /proc/meminfo | grep -i s...
📤 [775071081] 531 chars in 32.0s
🤖 [775071081] Calling Pi (continue session)...
📤 [775071081] 493 chars in 9.1s
+49 -9
View File
@@ -506,26 +506,66 @@ bot.on("message:text", async (ctx) => {
// ─── Error handler ─── // ─── Error handler ───
bot.catch((err) => { bot.catch((err) => {
console.error("Bot error:", err.message || err); const msg = err.message || String(err);
// 409 is expected during restart — ignore it
if (msg.includes("409") || msg.includes("Conflict")) {
console.log("⚠️ Telegram 409 conflict (normal during restart, retrying...)");
return;
}
console.error("Bot error:", msg);
}); });
// ─── Startup ─── // ─── Startup ───
console.log("🤖 CharityRight DevOps Bot starting..."); console.log("🤖 CharityRight DevOps Bot starting...");
// Clear stale updates const BOT_API = `https://api.telegram.org/bot${BOT_TOKEN}`;
await bot.api.getUpdates({ offset: -1, limit: 1 }).catch(() => {}); let pollOffset = 0;
let running = true;
bot.start({ async function pollLoop() {
onStart: () => console.log("✅ Bot online — polling for messages"), console.log("✅ Bot online — polling for messages");
drop_pending_update: true,
}); while (running) {
try {
const res = await fetch(`${BOT_API}/getUpdates?offset=${pollOffset}&limit=100&timeout=30`);
const data = (await res.json()) as any;
if (!data.ok) {
if (data.error_code === 409) {
console.log("⚠️ 409 conflict — waiting 10s...");
await new Promise((r) => setTimeout(r, 10_000));
continue;
}
console.error("Poll error:", data.description);
await new Promise((r) => setTimeout(r, 3000));
continue;
}
for (const update of data.result || []) {
pollOffset = update.update_id + 1;
try {
await bot.handleUpdate(update);
} catch (e: any) {
console.error("Handler error:", e.message?.substring(0, 200));
}
}
} catch (e: any) {
console.error("Network error:", e.message?.substring(0, 100));
await new Promise((r) => setTimeout(r, 3000));
}
}
}
await pollLoop();
// Cleanup on exit
const cleanup = () => { const cleanup = () => {
console.log("🛑 Shutting down..."); console.log("🛑 Shutting down...");
running = false;
for (const [, controller] of activeProcesses) controller.abort(); for (const [, controller] of activeProcesses) controller.abort();
bot.stop();
process.exit(0); process.exit(0);
}; };
process.on("SIGINT", cleanup); process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup); process.on("SIGTERM", cleanup);
process.on("unhandledRejection", (err: any) => {
console.error("Unhandled:", err?.message?.substring(0, 200) || err);
});