# Signal Forwarding Debugging Guide Systematic approach to diagnose "信号没收到" (signal not received) complaints. ## The Chain ``` Source Channel → TelegramForwarder → Target Group → Hermes → User DM (1) (2) (3) (4) (5) ``` Failure at ANY link breaks the chain. Debug from left to right. ## Step 1: Forwarder Receiving Messages? ```bash docker logs telegram-forwarder 2>&1 | grep "收到新消息.*实盘监控" | tail -5 ``` - **Has entries** → Forwarder is receiving from source. Go to Step 2. - **No entries** → Source channel not bound, or Telethon session expired. Check DB: ```bash docker exec telegram-forwarder python3 -c " import sqlite3 conn = sqlite3.connect('/app/db/forward.db') for r in conn.execute('SELECT id, telegram_chat_id, name FROM chats'): print(r) conn.close()" ``` ## Step 2: Keyword Matching? ```bash docker logs telegram-forwarder 2>&1 | grep -E "正则匹配成功|未匹配" | tail -5 ``` - `正则匹配成功` → Match succeeded. Go to Step 3. - `未匹配到普通白名单关键词` → Signal format changed, or regex doesn't match. - Check the actual signal text: `docker logs telegram-forwarder 2>&1 | grep "内容:" | tail -5` - Common issue: signal uses different brackets (e.g., `[币种]` vs `【币种】`) - Common issue: multi-line signals — `.*` doesn't match `\n`, use `[\s\S]*` instead ## Step 3: Forwarder Sending to Group? ```bash docker logs telegram-forwarder 2>&1 | grep -E "已发送|成功获取目标" | tail -5 ``` - `消息已发送到: XXX` → Forward succeeded. Go to Step 4. - No send logs after match → Check for errors: ```bash docker logs telegram-forwarder 2>&1 | grep -iE "error|invalid Peer|banned|forbidden" | tail -5 ``` - `An invalid Peer was used` → Group migrated to supergroup, ID changed - `Bots can't send messages` → Trying to send to a bot, not a group ## Step 4: Hermes Receiving from Group? **FIRST: Check for competing gateway processes** (most common hidden cause): ```bash ps aux | grep "gateway\|openclaw" | grep -v grep ``` If you see an old `openclaw ... gateway` or duplicate `hermes gateway` process → **KILL IT FIRST**. Two processes sharing one bot token = permanent polling failure. See "Polling Conflict — Scenario A" below. Then check polling status: ```bash # Check for polling conflict journalctl --user -u hermes-gateway --since "10 min ago" --no-pager | grep -i "conflict" # If found → see "Polling Conflict" section below # Check if "✓ telegram connected" appears in logs journalctl --user -u hermes-gateway --since "5 min ago" --no-pager | grep "✓ telegram" # If NOT found → adapter didn't connect (check for competing processes or conflict) # Check gateway.log for inbound messages from the group grep "inbound message" ~/.hermes/logs/gateway.log | grep "1003966251111" | tail -5 ``` - **Polling conflict found** → Stop gateway, wait 30s, start fresh (see "Polling Conflict" section) - **Has entries** → Hermes receives group messages. Go to Step 5. - **No entries, no conflict** → Hermes can't see group messages. Check: 1. Bot privacy mode: `curl -s --proxy http://127.0.0.1:7890 "https://api.telegram.org/bot/getMe" | grep can_read_all_group_messages` - Must be `true`. If `false`, disable in @BotFather and **kick+re-add** bot to group. 2. `free_response_chats` config (NOT `free_response_channels`): ```bash grep -A5 "extra:" ~/.hermes/config.yaml | grep free_response ``` Must be under `telegram.extra.free_response_chats`, not top-level. 3. Check if bot is actually in the group. ## Step 5: Hermes Processing and Responding? ```bash grep "1003966251111" ~/.hermes/logs/gateway.log | grep -E "response ready|Suppressing" | tail -5 ``` - `response ready` → Hermes processed and responded. Check delivery. - `inbound` but no `response` → Agent failed or timed out. Check errors.log: ```bash tail -20 ~/.hermes/logs/errors.log ``` ## Quick One-Liner Diagnostic ```bash echo "=== Forwarder ===" && \ docker logs telegram-forwarder 2>&1 | grep -cE "正则匹配成功|已发送" && \ echo "=== Hermes ===" && \ grep -c "inbound.*1003966251111" ~/.hermes/logs/gateway.log 2>/dev/null && \ echo "=== Errors ===" && \ tail -3 ~/.hermes/logs/errors.log 2>/dev/null ``` ## Log Pattern Reference | Pattern | Meaning | Where | |---------|---------|-------| | `收到新消息 来自聊天: XXX` | Forwarder received message | forwarder logs | | `正则匹配成功` | Keyword regex matched | forwarder logs | | `未匹配到普通白名单关键词` | Keyword didn't match | forwarder logs | | `所有白名单条件都满足,允许转发` | Forwarding allowed | forwarder logs | | `消息已发送到: XXX` | Message sent to target | forwarder logs | | `过滤器识别到机器人消息,忽略处理` | Bot message filtered (normal) | forwarder logs | | `inbound message: platform=telegram chat=XXX` | Hermes received message | gateway.log | | `response ready: platform=telegram chat=XXX` | Hermes responded | gateway.log | | `Suppressing normal final send` | Response already delivered via stream | gateway.log | ## Common Scenarios ### "昨天能收到,今天收不到" - Check if source channel stopped sending (forwarder logs) - Check if Telethon session expired (container restart fixes this) - Check if signal format changed ### "有信号进来但没收到你的消息" - Could be timing: signal hasn't arrived yet, or arrived but user expected Hermes response - Quick check: `docker logs telegram-forwarder 2>&1 | grep "收到新消息.*实盘监控" | tail -1` — compare timestamp to current time. If >30min stale, the signal may not have reached the forwarder. - If forwarder received and forwarded but no Hermes response: check `tail -20 ~/.hermes/logs/gateway.log | grep inbound` — if no recent group messages, the bot may not be receiving (privacy mode). - **Bot message filtering**: Hermes may ignore messages sent by other bots. Always test with a human account, not the forwarder bot. - **⚠️ Check for competing gateway processes**: `ps aux | grep "gateway\|openclaw" | grep -v grep` — if an old OpenClaw Gateway or duplicate Hermes process is running, it steals the bot token polling session and Hermes NEVER receives any group messages. This was the actual root cause for a user whose group messages had NEVER worked for over a week. ### "信号在频道里能看到,但没转发到群" - Step 2 failure: keyword mismatch - Check actual signal text vs regex pattern ### "信号转发到群了,但Hermes没回复" - Step 4 failure: privacy mode or config - Most common: bot privacy mode is ON ### "重启了 gateway 还是不处理群消息"(Polling Conflict — 两种原因) **This is the #1 hidden cause.** There are TWO distinct polling conflict scenarios: #### Scenario A: Competing gateway processes (most dangerous) Another process is using the same bot token for `getUpdates`. This causes PERMANENT failure — no amount of restarting Hermes will help until the other process is killed. **Diagnosis**: ```bash # 1. Check for competing processes ps aux | grep "gateway\|openclaw" | grep -v grep # Look for: node .../openclaw/dist/index.js gateway --port 18789 # Or: another hermes gateway run # 2. Test getUpdates directly (bypass adapter) python3 -c " import json, urllib.request with open('/home/openclaw/.hermes/.env') as f: for line in f: if line.startswith('TELEGRAM_BOT_TOKEN'): token = line.strip().split('=',1)[1] break proxy = urllib.request.ProxyHandler({'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}) opener = urllib.request.build_opener(proxy) try: req = urllib.request.Request(f'https://api.telegram.org/bot{token}/getUpdates?timeout=1') resp = opener.open(req, timeout=5) data = json.loads(resp.read()) print(f'getUpdates: {len(data.get(\"result\", []))} updates (no conflict = nobody polling)') except Exception as e: if '409' in str(e): print('409 Conflict — adapter IS polling (normal)') else: print(f'Error: {e}') " ``` **Key diagnostic signal**: - `getUpdates` returns 0 without 409 conflict → **NOBODY is polling** → competing process consumed and dropped updates - `getUpdates` returns 409 → adapter IS polling (conflict is expected, this is normal) - `getUpdates` returns updates → adapter is NOT polling AND competing process dropped off → restart Hermes **Fix**: Kill the stale process FIRST, then restart Hermes: ```bash kill systemctl --user restart hermes-gateway ``` **Verify**: `journalctl --user -u hermes-gateway --since "1 min ago" | grep "✓ telegram connected"` — must appear after restart. #### Scenario B: `hermes gateway restart` race condition The restart command sends SIGTERM and immediately starts a new process. Telegram's servers keep the old polling session alive for ~30 seconds. The new process tries to poll → conflict → retry loop. **Diagnosis**: ```bash journalctl --user -u hermes-gateway --since "5 min ago" --no-pager | grep -i "conflict" ``` **Fix**: ```bash systemctl --user stop hermes-gateway sleep 30 # critical: wait for Telegram session to expire systemctl --user start hermes-gateway ``` #### How to tell which scenario ```bash # Check how long the gateway has been running systemctl --user status hermes-gateway | grep "Active:" # If running for days/weeks but group messages NEVER worked → Scenario A # If just restarted and immediately getting conflicts → Scenario B # Check for stale processes ps aux | grep "gateway" | grep -v grep | grep -v $(pgrep -f "hermes_cli.main gateway") # If you see old processes → Scenario A ``` ## Gateway Logs (Binary Format — Use strings!) Gateway logs at `~/.hermes/logs/gateway.log` are **binary format**. Use `strings` to extract: ```bash strings ~/.hermes/logs/gateway.log | tail -30 strings ~/.hermes/logs/gateway.log | grep "inbound message" | tail -10 strings ~/.hermes/logs/gateway.log | grep "" | tail -10 strings ~/.hermes/logs/gateway.log | grep "Connected to" ``` ## ⚠️ Testing: Use Human Account **Never test with bot-sent messages.** Bots cannot see their own messages via getUpdates. Always use a human Telegram account to send test messages to the group or source channel. ## Agent-Side Filtering Architecture When the user says "收所有的消息,在处理消息这边来处理规则过滤": 1. Forwarder: `.*` whitelist (pass everything) 2. Agent channel_prompts: classify messages as A(signal)/B(confirm)/C(close)/D(ignore) 3. Only process A/B/C, silently ignore D Benefits: source format changes only require updating the agent prompt, not the forwarder database.