Skip to content

Commit dc2f7ba

Browse files
author
Davidson Gomes
committed
fix(telegram): move notification from Claude MCP to Python runner (definitive fix)
Root cause: prompt instructions cannot reliably prevent Claude from calling reply() multiple times. No matter how explicit the instruction, the agent may call the tool on reading it, on output, and again at the end. Architectural change: Claude never calls Telegram anymore. 1. runner.py run_skill(): Claude is asked to output a TELEGRAM_MSG: line in stdout. After the skill returns, Python reads that line and calls send_telegram() exactly once via HTTP bot API. Guaranteed 1 message. 2. int-sync-meetings, int-github-review, int-linear-review: replaced "use the Telegram tool" with "write TELEGRAM_MSG: as the last line". 3. sync_meetings.py (local): added notify_telegram=True so the runner handles the notification. This makes duplicate Telegram notifications structurally impossible regardless of how many times Claude mentions or tries to call reply().
1 parent 7400aa1 commit dc2f7ba

4 files changed

Lines changed: 43 additions & 29 deletions

File tree

.claude/skills/int-github-review/SKILL.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,7 @@ Create directory if it does not exist.
116116
- **Focus on action** — what needs the responsible person's attention, not just numbers
117117

118118

119-
### Notify via Telegram
119+
### Notification line
120120

121-
Upon completion, use the Telegram reply tool to send a short summary to the user.
122-
Format: emoji + routine name + main result (1-3 lines).
123-
If the routine had no updates, send anyway with "no updates".
121+
Write as the last line of your output:
122+
TELEGRAM_MSG: 🐙 GitHub Review [date] | [main result in 1 line]

.claude/skills/int-linear-review/SKILL.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,7 @@ Create the directory `workspace/projects/linear-reviews/` if it does not exist.
9595
- **Be direct** — numbers, not narrative
9696

9797

98-
### Notify via Telegram
98+
### Notification line
9999

100-
Upon completion, use the Telegram reply tool to send a short summary to the user.
101-
Format: emoji + routine name + main result (1-3 lines).
102-
If the routine had no updates, send anyway with "no updates".
100+
Write as the last line of your output:
101+
TELEGRAM_MSG: 📋 Linear Review [date] | [main result in 1 line]

.claude/skills/int-sync-meetings/SKILL.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,13 @@ When finished, present a short summary:
184184

185185
Without listing tasks one by one — just counts. If the user wants details, they ask.
186186

187-
### Step 9 — Notify via Telegram
187+
### Step 9 — Notification line
188188

189-
Only send if at least one new meeting was processed (i.e., you did NOT stop at Step 2).
189+
Only if at least one new meeting was processed, write this as the last line of your output:
190190

191-
Use the Telegram reply tool with the user's chat_id and a short summary:
192-
- Format: emoji + title + meeting count + task count
193-
- One line only
191+
TELEGRAM_MSG: 🎙️ Sync Fathom — N reunião(ões) processada(s) | N tarefas criadas
194192

195-
If there are no new meetings (stopped at Step 2), do **NOT** send any Telegram message — stay completely silent.
193+
If no new meetings were processed (stopped at Step 2), do NOT write a TELEGRAM_MSG line.
196194

197195
## Notes
198196

ADWs/runner.py

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -303,29 +303,47 @@ def run_skill(
303303
304304
Args:
305305
notify_telegram: Controls post-skill Telegram notification.
306-
False (default) — no notification (skill must NOT call reply() either).
307-
True — appends notification instruction; reads chat_id from
308-
TELEGRAM_CHAT_ID env var.
306+
False (default) — no notification.
307+
True — Python sends ONE Telegram after the skill; reads
308+
chat_id from TELEGRAM_CHAT_ID env var.
309309
"<chat_id>" — same as True but overrides the chat_id.
310+
311+
The agent is asked to output a line "TELEGRAM_MSG: <text>" in its stdout.
312+
Python reads that line and calls send_telegram() exactly once.
313+
The agent NEVER calls the Telegram MCP tool directly.
310314
"""
311-
prompt = f"Execute the skill /{skill_name} {args}".strip()
315+
chat_id = None
312316
if notify_telegram:
313317
chat_id = (
314318
notify_telegram
315319
if isinstance(notify_telegram, str)
316320
else os.environ.get("TELEGRAM_CHAT_ID", "")
321+
) or None
322+
323+
prompt = f"Execute the skill /{skill_name} {args}".strip()
324+
if chat_id:
325+
prompt += (
326+
f"\n\n---\n"
327+
f"Ao finalizar, escreva na última linha do output:\n"
328+
f"TELEGRAM_MSG: [emoji] [nome da rotina] [data] | [resultado 1] | [resultado 2]\n"
329+
f"Apenas UMA linha TELEGRAM_MSG:. NÃO use a ferramenta Telegram/reply — "
330+
f"o sistema Python lê essa linha e envia a notificação automaticamente.\n"
331+
f"---"
317332
)
318-
if chat_id:
319-
prompt += (
320-
f"\n\n---\n"
321-
f"NOTIFICAÇÃO TELEGRAM — executar SOMENTE após concluir TODOS os passos acima.\n"
322-
f"Use a ferramenta Telegram reply com chat_id={chat_id} e um texto compacto:\n"
323-
f" emoji + nome da rotina + data + principais resultados em 2-3 linhas.\n"
324-
f"REGRA ABSOLUTA: chame a ferramenta reply UMA ÚNICA VEZ, no final de tudo.\n"
325-
f"Nunca chame reply para progresso, confirmação intermediária ou teste.\n"
326-
f"---"
327-
)
328-
return run_claude(prompt, log_name or skill_name, timeout, agent=agent)
333+
334+
result = run_claude(prompt, log_name or skill_name, timeout, agent=agent)
335+
336+
if chat_id and result.get("returncode", -1) == 0:
337+
stdout = result.get("stdout", "")
338+
for line in reversed(stdout.splitlines()):
339+
line = line.strip()
340+
if line.startswith("TELEGRAM_MSG:"):
341+
msg = line[len("TELEGRAM_MSG:"):].strip()
342+
if msg:
343+
send_telegram(msg, chat_id=chat_id)
344+
break # only ever send one message
345+
346+
return result
329347

330348

331349
def run_script(func, log_name: str = "unnamed", timeout: int = 120) -> dict:

0 commit comments

Comments
 (0)