Skip to content

robonuggets/telegram-hook-fix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Telegram Hook Fix

Two Claude Code hooks that fix the most common failures with the official Telegram plugin. Both run as deterministic Python — zero Claude tokens, zero model inference.

Drop these in if your Telegram plugin throws 409 Conflict between sessions, or if you've ever caught your agent silently dropping a Telegram reply.

What's in the box

Hook event Name What it does
SessionStart Telegram Cleanup Kills stale Telegram processes from the last session so the new one starts clean.
Stop Reply Guard Checks every Telegram message got a reply before the session ends — so nothing gets dropped.

Why this exists

The 409 Conflict bug. Telegram allows exactly one getUpdates consumer per bot token. On Windows the plugin's clean-shutdown path is unreliable — when a terminal is X'd out, the machine sleeps, or a session crashes hard, the bot subprocess can linger and hold the token. The next session then fails with 409 Conflict: terminated by other getUpdates request. Telegram Cleanup fixes this by reading the plugin's bot.pid file at SessionStart and force-killing whatever PID it points to before the new MCP connection tries to grab the token.

The dropped-reply bug. The Telegram plugin only delivers messages to the agent as terminal text — it never reaches the user's chat unless the agent calls the reply tool. Sometimes the agent forgets. Reply Guard runs at Stop, walks the transcript backwards to find the most recent Telegram inbound, and confirms a reply tool call followed it. If not, it sends a fallback message directly via Telegram's Bot API so the user knows their message was received but the agent dropped it.

Quick start

git clone https://github.com/robonuggets/telegram-hook-fix

Then:

  1. Copy both hooks/*.py files to a stable location on your machine — ~/.claude/hooks/ is a sensible default.
  2. Open your Claude Code settings (~/.claude/settings.json) and merge the hooks block from settings.example.json. Update the absolute paths to match where you placed the scripts.
  3. Restart Claude Code. Done.

File tree

telegram-hook-fix/
├── README.md
├── LICENSE
├── settings.example.json
├── .claude/skills/telegram-hook-fix/SKILL.md
└── hooks/
    ├── telegram-preemptive-kill.py    (SessionStart · Telegram Cleanup)
    └── telegram-reply-guard.py        (Stop · Reply Guard)

Settings snippet

settings.example.json contains the hook config to merge into your existing ~/.claude/settings.json. The shape:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python /absolute/path/to/hooks/telegram-preemptive-kill.py \"~/.claude/channels/telegram\"",
            "timeout": 10
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python /absolute/path/to/hooks/telegram-reply-guard.py \"~/.claude/channels/telegram\"",
            "timeout": 15
          }
        ]
      }
    ]
  }
}

The path passed as the second argument is the channel state directory — where the Telegram plugin keeps its bot.pid and .env. Default for the official plugin on most systems is ~/.claude/channels/telegram. If you've configured a different location, point both hooks at it.

If you already have a hooks block in your settings, merge — don't overwrite. Multiple SessionStart / Stop hooks can coexist; just append to the hooks array.

How it works

Telegram Cleanup (telegram-preemptive-kill.py)

  • Runs at SessionStart, before MCP servers spin up.
  • Reads <channel_state_dir>/bot.pid.
  • Force-kills that PID via taskkill /F on Windows. (POSIX: swap for kill -9.)
  • Removes the pid file so the plugin starts fresh.
  • Exits 0 always — a hook crash must never block session start.
  • Logs to <channel_state_dir>/preemptive-kill.log.

Reply Guard (telegram-reply-guard.py)

  • Runs at Stop, after the agent's turn ends.
  • Reads transcript_path from the hook payload (Claude Code's session transcript).
  • Walks backwards to find the most recent user message containing a <channel source="plugin:telegram:telegram" chat_id="..."> tag.
  • From there, walks forward looking for an assistant tool_use whose name is mcp__plugin_telegram_telegram__reply.
  • If found → no-op (logged).
  • If not found → sends a fallback message directly to that chat_id via https://api.telegram.org/bot<TOKEN>/sendMessage, so the user sees: ⚠️ Session ended without a Telegram reply — I may have forgotten to use the reply tool. Ping me again if you need a response.
  • Token lookup order: $TELEGRAM_BOT_TOKEN env var, then <channel_state_dir>/.env.
  • Exits 0 always.
  • Logs to <channel_state_dir>/reply-guard.log.

Platform notes

The Cleanup hook uses taskkill /F /PID <pid> — Windows-native. If you're on macOS or Linux, swap that subprocess call for os.kill(pid, signal.SIGKILL) or subprocess.run(["kill", "-9", str(pid)]). Pull requests welcome.

The Reply Guard is platform-agnostic — pure Python stdlib, only needs internet to hit the Bot API.

Troubleshooting

  • Hook doesn't fire at all — confirm python resolves on your PATH. Run python --version in a fresh terminal. If it doesn't resolve, swap python for the absolute path to your interpreter inside settings.json.
  • Reply Guard never sends a fallback — the script needs the bot token. It checks $TELEGRAM_BOT_TOKEN first, then <channel_state_dir>/.env. If neither is set, the fallback is silent — check <channel_state_dir>/reply-guard.log for a no token in env line.
  • Cleanup runs but I still see 409 — the plugin may have already grabbed the token before the hook ran. Kill the process manually once (taskkill /F /PID <pid> on Windows, kill -9 <pid> elsewhere), then start a fresh session.

License

CC BY 4.0 — free to use with attribution.

About

Two Claude Code hooks that fix the most common Telegram plugin failures: stale bot processes (409 Conflict) + dropped replies. Zero tokens, runs as local Python.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages