An open API service indexing awesome lists of open source software.

https://github.com/ywatanabe1989/claude-code-telegrammer

Screen-based auto-responder watchdog for Claude Code TUI — autonomous Telegram agent with just GNU Screen and Bash
https://github.com/ywatanabe1989/claude-code-telegrammer

automation claude-code screen telegram watchdog

Last synced: 2 months ago
JSON representation

Screen-based auto-responder watchdog for Claude Code TUI — autonomous Telegram agent with just GNU Screen and Bash

Awesome Lists containing this project

README

          

# claude-code-telegrammer



SciTeX

Custom Telegram MCP server + TUI auto-responder for running Claude Code as an autonomous Telegram agent


PyPI version
Documentation
Tests
License: AGPL-3.0


Documentation ·
pip install claude-code-telegrammer

---

## Problem and Solution

#
Problem
Solution

1

Hardcoded paths

The official plugin hardcodes ~/.claude/ as its state directory (#851), making it impossible to run multiple bots or customize where access.json lives.

Configurable state directory

All state (DB, lock, access config) lives under CLAUDE_CODE_TELEGRAMMER_TELEGRAM_STATE_DIR. Run as many bots as you want, each with its own isolated state.

2

409 Conflict crashes

No single-instance guard — multiple sessions polling the same bot get 409 errors and crash each other (#1075).

PID-based lock

Automatic single-instance enforcement via PID lock file. Second instance detects the conflict and waits instead of crashing.

3

Zombie CPU consumption

After session ends, the plugin process lingers at 100% CPU — requires manual kill (#1146).

Clean shutdown

Exits gracefully on stdin close, SIGTERM, or SIGINT. No zombies, no manual cleanup.

4

Only 3 basic tools

The official plugin provides just send, get_updates, and set_reaction — no history, no search, no file handling, no message editing.

10 MCP tools

reply, react, edit_message, get_history, get_unread, mark_read, download_attachment, send_document, search_messages, get_context — everything an autonomous agent needs.

5

No message persistence

Messages vanish after delivery. No way to search past conversations, track read status, or build context from history.

SQLite message store

All messages persisted in WAL-mode SQLite with full-text search, reply threading (reply_to_message_id), read/replied tracking, and attachment metadata.

6

No access control for groups

Basic allowlist only — no per-group policies, no hot-reload when config changes.

DM + group policies

Allowlist-based access control with separate DM and group chat policies via access.json, hot-reloaded on file change (mtime-based).

7

No attachment support

Cannot download inbound files or upload documents to chats.

Full attachment handling

Inbound photos, documents, voice, audio, and video are auto-downloaded. Upload local files via send_document tool.

8

Sessions stall unattended

Claude Code halts at permission prompts or idle states with no way to recover — the agent just stops working.

TUI Watchdog

Polls GNU Screen buffer, detects TUI state via pattern matching, sends keystrokes to auto-accept prompts and re-engage on idle. Throttled with burst limits.

Table 1. Eight issues with the official Telegram plugin (as of April 2026) and how claude-code-telegrammer addresses each. These problems make the official plugin unusable for production autonomous agents.

### Architecture

1. **Custom Telegram MCP Server** (`ts/`) -- Self-contained Bun + MCP server. 10 tools, SQLite persistence, allowlist access control, attachment handling, reaction support. Incoming messages acknowledged with 📩. Built-in responsiveness policy directs the agent to reply immediately and delegate heavy work to background subagents.

2. **TUI Watchdog** (`lib/`) -- Polls a GNU Screen session, detects Claude Code's TUI state via pattern matching, and sends keystrokes to keep the agent running unattended (auto-accepts permission prompts, re-engages on idle). Throttled with burst limits to prevent runaway responses. Orchestration handled by [scitex-agent-container](https://github.com/ywatanabe1989/scitex-agent-container).

MCP Tools (10)

| Tool | Description |
|------|-------------|
| `reply` | Reply on Telegram. Supports threading (`reply_to`), auto-marks inbound as read. Inbound reply-to-message references are tracked and forwarded. |
| `react` | Add an emoji reaction to a message. Inbound reactions (`message_reaction`) are also delivered as channel notifications. |
| `edit_message` | Edit a previously sent bot message. |
| `get_history` | Retrieve message history for a chat from local SQLite. |
| `get_unread` | List unread inbound messages, optionally filtered by `chat_id`. |
| `mark_read` | Mark messages as read by `chat_id` or `message_ids`. |
| `download_attachment` | Download a Telegram file by `file_id`, returns local path. |
| `send_document` | Upload a local file to a Telegram chat. |
| `search_messages` | Text search across stored messages. |
| `get_context` | Recent conversation formatted as compact text for LLM context. |

### Important: Bot Token Exclusivity

This MCP server **must be the sole consumer** of its configured Telegram bot token. The Telegram Bot API allows only one `getUpdates` long-polling connection per token.

**What happens with duplicate consumers:**

| Scenario | Symptom | Detection |
|----------|---------|-----------|
| Two pollers start simultaneously | One gets 409, the other wins silently | The loser sees `409 Conflict` in logs |
| Two pollers start sequentially | Both appear to work, but only one receives messages | **No error** — the other poller gets empty responses forever |
| Webhook active + poller | Poller gets nothing | **No error** — Telegram ignores `getUpdates` when webhook is set |

**Why 409 detection alone is insufficient:** The Telegram API does not reliably return 409 for all conflict cases. When two consumers poll sequentially (not overlapping), both connections succeed — one simply receives all messages while the other gets none, with no error. The server performs a `timeout=3` preflight check at startup to catch overlapping polls, but this cannot detect the sequential case.

**If messages aren't arriving:**
1. Check if another process is polling the same token: `ps aux | grep telegram-server`
2. Check if a webhook is set: `curl https://api.telegram.org/bot/getWebhookInfo`
3. Use a separate bot token per component (recommended)
4. Or disable the other consumer

**Alternative: Webhook mode via scitex-orochi.** If you run [scitex-orochi](https://github.com/ywatanabe1989/scitex-orochi), it supports Telegram webhook mode (`POST /webhook/telegram/`) which eliminates polling conflicts entirely. Telegram pushes updates to a single HTTPS endpoint instead of competing pollers. See `SCITEX_OROCHI_TELEGRAM_WEBHOOK_URL` in the orochi documentation.

## Installation

### Prerequisites

- [Bun](https://bun.sh/) >= 1.0 (for the MCP server)
- GNU Screen (for watchdog, optional)

### Install

```bash
git clone https://github.com/ywatanabe1989/claude-code-telegrammer.git
cd claude-code-telegrammer/ts && bun install
```

## Quickstart

### Get a Telegram Bot Token

1. Open Telegram and message [@BotFather](https://t.me/BotFather)
2. Send `/newbot`, then enter a name (e.g., `Claude Code Telegrammer`) and a username (e.g., `ClaudeCodeTelegrammerBot`)
3. BotFather replies with your token: `123456789:AAH...`
4. Verify your token works:
```bash
curl -s "https://api.telegram.org/bot/getMe"
# Should return {"ok":true,"result":{"is_bot":true,...}}
```
5. Open your bot (e.g., [t.me/ClaudeCodeTelegrammerBot](https://t.me/ClaudeCodeTelegrammerBot)) and send any message to start a conversation

### Register MCP Server with Claude Code

Copy the example and fill in your values (`.mcp.json` is gitignored):

```json
{
"mcpServers": {
"claude-code-telegrammer": {
"type": "stdio",
"command": "bun",
"args": ["run", "/path/to/claude-code-telegrammer/ts/telegram-server.ts"],
"env": {
"CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN": "123456789:AAH...",
"CLAUDE_CODE_TELEGRAMMER_TELEGRAM_ALLOWED_USERS": "YOUR_TELEGRAM_USER_ID",
"CLAUDE_CODE_TELEGRAMMER_TELEGRAM_STATE_DIR": "~/.claude-code-telegrammer"
}
}
}
}
```

```bash
cp .mcp.json.example .mcp.json
# Edit .mcp.json with your token, user ID, and paths
```

Find your Telegram user ID by messaging [@userinfobot](https://t.me/userinfobot).

### Run

```bash
claude \
--dangerously-skip-permissions \
--dangerously-load-development-channels server:claude-code-telegrammer
```

You should see `Listening for channel messages from: server:claude-code-telegrammer` in the Claude Code TUI. Send a message from Telegram to your bot — Claude Code will receive it as a channel notification.

## Interfaces

MCP Server -- for AI Agents

Start command:
```bash
bun run ts/telegram-server.ts
```

10 tools exposed via MCP stdio protocol. See [MCP Tools](#solution) above. The server's MCP instructions include a responsiveness policy that directs the agent to acknowledge messages immediately and delegate heavy work to background subagents.

Skills -- for AI Agent Discovery

Skills are bundled at `src/claude_code_telegrammer/_skills/claude-code-telegrammer/SKILL.md`.

## Architecture

```
User (Telegram)
|
| Bot API (getUpdates long-polling)
v
┌──────────────────────────────────────────────────────────────┐
│ Custom Telegram MCP Server (ts/telegram-server.ts) │
│ Bun + @modelcontextprotocol/sdk │
│ │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌────────────┐ │
│ │ Poller │ │ Store │ │ Tools │ │ Attachments│ │
│ │ (long │ │ (SQLite │ │ (10 MCP │ │ (download │ │
│ │ poll) │ │ WAL) │ │ tools) │ │ queue) │ │
│ └─────────┘ └─────────┘ └──────────┘ └────────────┘ │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
│ │ Access │ │ Config │ │ Lock │ │
│ │ (allow- │ │ (env │ │ (PID │ │
│ │ list) │ │ vars) │ │ file) │ │
│ └─────────┘ └─────────┘ └──────────┘ │
└──────────────────────┬───────────────────────────────────────┘
│ MCP stdio
v
┌──────────────────────────────────────────────────────────────┐
│ Claude Code (in GNU Screen session) │
│ --mcp-config points to the custom MCP server │
└──────────────────────┬───────────────────────────────────────┘
│ screen buffer
v
┌──────────────────────────────────────────────────────────────┐
│ Watchdog (claude-code-telegrammer-watchdog) │
│ Polls screen buffer every 1.5s │
│ Detects: y/n prompt -> "1", y/y/n -> "2", idle -> cmd │
│ Throttled: burst limit, same-state delay, min interval │
└──────────────────────────────────────────────────────────────┘
```

State Detection

| State | Pattern | Response |
|-------|---------|----------|
| `running` | `(esc to interrupt)`, `tokens ·`, `ing...` | No action |
| `y_n` | `1. Yes` + `3. No` (two-choice prompt) | Send `1` (accept) |
| `y_y_n` | `2. Yes, and...` / `2. Yes, allow...` / `2. Yes, don't ask...` | Send `2` (accept all) |
| `waiting` | Cooking puns (`Crafted for`, etc.), empty `>` prompt, idle hints | Send configurable command |

Response throttling: minimum interval between responses, burst limit (10 in 3s window), same-state delay.

Configuration (Environment Variables)

**MCP Server:**

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN` | Yes | -- | Telegram Bot API token |
| `CLAUDE_CODE_TELEGRAMMER_TELEGRAM_STATE_DIR` | No | `~/.claude-code-telegrammer` | Directory for SQLite DB, access.json, lock file |
| `CLAUDE_CODE_TELEGRAMMER_TELEGRAM_ALLOWED_USERS` | No | -- | Comma-separated Telegram user IDs for DM allowlist |
| `CLAUDE_CODE_TELEGRAMMER_TELEGRAM_HOST_NAME` | No | `os.hostname()` | Hostname stored with each message |
| `CLAUDE_CODE_TELEGRAMMER_TELEGRAM_PROJECT` | No | `process.cwd()` | Project path stored with each message |
| `CLAUDE_CODE_TELEGRAMMER_TELEGRAM_AGENT_ID` | No | `'telegram'` | Agent identifier stored with each message |

**Watchdog:**

| Variable | Default | Description |
|----------|---------|-------------|
| `CLAUDE_CODE_TELEGRAMMER_SESSION` | `claude-code-telegrammer` | GNU Screen session name |
| `CLAUDE_CODE_TELEGRAMMER_WATCHDOG_INTERVAL` | `1.5` | Poll interval in seconds |
| `CLAUDE_CODE_TELEGRAMMER_RESP_Y_N` | `1` | Response for y/n prompts |
| `CLAUDE_CODE_TELEGRAMMER_RESP_Y_Y_N` | `2` | Response for y/y/n prompts |
| `CLAUDE_CODE_TELEGRAMMER_RESP_WAITING` | `/speak-and-call` | Response when idle/waiting |

SQLite Schema (v2)

All messages persisted in `$CLAUDE_CODE_TELEGRAMMER_TELEGRAM_STATE_DIR/messages.db` using WAL mode.

**messages table:** direction, chat_id, message_id, user_id, username, text, timestamps (telegram_ts, received_at, read_at, replied_at), threading (reply_to_message_id, reply_to_row_id), identity (host, project, agent_id, bot_token_hash), raw_json.

**attachments table:** message_row_id (FK), kind, file_id, file_name, mime_type, file_size, local_path, downloaded_at.

**meta table:** key-value store for schema_version, update_offset.

Integration with scitex-agent-container

For YAML-based agent orchestration (screen sessions, watchdog lifecycle, restart policies), see [scitex-agent-container](https://github.com/ywatanabe1989/scitex-agent-container).

Access Control

Managed via `access.json` in `$CLAUDE_CODE_TELEGRAMMER_TELEGRAM_STATE_DIR`:

```json
{
"dmPolicy": "allowlist",
"allowFrom": ["123456789"],
"groups": {
"-100123456": {
"requireMention": true,
"allowFrom": ["123456789"]
}
}
}
```

Merged with `CLAUDE_CODE_TELEGRAMMER_TELEGRAM_ALLOWED_USERS` env var at runtime. Mtime-based caching means edits take effect without restart.

## Part of SciTeX

claude-code-telegrammer is part of [**SciTeX**](https://scitex.ai). It provides the Telegram communication layer and TUI watchdog used by [scitex-agent-container](https://github.com/ywatanabe1989/scitex-agent-container) for autonomous agent operation.

```
┌─────────────────────────────────────────────────────────┐
│ scitex-orochi — agent definitions, dashboard │
└──────────────────────────┬──────────────────────────────┘
v
┌─────────────────────────────────────────────────────────┐
│ scitex-agent-container — lifecycle, health, restart │
└──────────────────────────┬──────────────────────────────┘
v
┌─────────────────────────────────────────────────────────┐
│ claude-code-telegrammer <-- YOU ARE HERE │
│ MCP server: Telegram API, message DB, 10 tools │
│ Watchdog: TUI auto-response, screen polling │
└─────────────────────────────────────────────────────────┘
```

## References

- [Claude Code Channels](https://docs.anthropic.com/en/docs/claude-code/channels) -- Official documentation for Claude Code's channel system
- [Official Telegram Plugin](https://github.com/anthropics/claude-code/tree/main/plugins/telegram) -- The `plugin:telegram@claude-plugins-official` source code
- [#851: STATE_DIR not respected](https://github.com/anthropics/claude-code/issues/851) -- Hardcoded access.json path
- [#1075: 409 Conflict errors](https://github.com/anthropics/claude-code/issues/1075) -- Multiple instances polling the same bot
- [#1146: Zombie CPU consumption](https://github.com/anthropics/claude-code/issues/1146) -- Runaway process after session ends
- [Telegram BotFather](https://t.me/BotFather) -- Create and manage Telegram bots
- [Telegram Bot API](https://core.telegram.org/bots/api) -- Official Bot API documentation
- [MCP Specification](https://modelcontextprotocol.io/) -- Model Context Protocol standard
- [claude-code-telegrammer Issues](https://github.com/ywatanabe1989/claude-code-telegrammer/issues) -- Bug reports and feature requests
- [claude-code-telegrammer Pull Requests](https://github.com/ywatanabe1989/claude-code-telegrammer/pulls) -- Contributions

>Four Freedoms for Research
>
>0. The freedom to **run** your research anywhere -- your machine, your terms.
>1. The freedom to **study** how every step works -- from raw data to final manuscript.
>2. The freedom to **redistribute** your workflows, not just your papers.
>3. The freedom to **modify** any module and share improvements with the community.
>
>AGPL-3.0 -- because we believe research infrastructure deserves the same freedoms as the software it runs on.

---


SciTeX