Control Claude Code from Telegram — approve permissions, answer questions, resume sessions, and manage AI coding agents from your phone.
CCGram is a self-hosted Telegram bot that bridges Claude Code to your phone. When Claude needs a permission, has a question, or finishes a task — you get a Telegram message with inline buttons to respond. Resume past conversations, start new sessions, and manage multiple AI coding agents — all without being at your keyboard.
Claude Code → ccgram hooks → Telegram bot → 📱 your phone
↑ ↓
└──── tmux or PTY injection ─────┘
- Permission approvals — Allow, Deny, or Always allow with a single tap
- Question answering — Select from Claude's options via inline buttons (single and multi-select)
- Smart notifications — Task completions, session start/end, and subagent activity — silent when you're at your terminal, instant when you're away
- Remote command routing — Send any command to any Claude session from Telegram
- Session management — List, switch between, and interrupt active sessions
- Resume conversations —
/resumereads your full Claude Code session history with conversation snippets — pick up any past conversation in one tap - Project launcher — Start Claude in any project directory with
/new myproject - Smart routing — Prefix matching, default workspace, reply-to routing
- Typing indicator — See when the bot is waiting for Claude to respond
- tmux optional — Falls back to a headless PTY session (
node-pty) when tmux is unavailable - One-command setup — Interactive wizard installs hooks, generates service file, starts bot
- Node.js 18+
- A Telegram bot token (from @BotFather)
- Your Telegram chat ID (from @userinfobot)
- tmux (optional — falls back to headless PTY via
node-ptywhen absent)
npx @jsayubi/ccgram initThe setup wizard will:
- Ask for your bot token and chat ID
- Install the bot to
~/.ccgram/ - Merge the required hooks into
~/.claude/settings.json - Generate and start a background service (launchd on macOS, systemd on Linux)
Then open Telegram and message your bot — Claude Code will now notify you remotely.
CCGram integrates with Claude Code hooks — shell scripts that Claude Code calls at key moments. Each hook script sends a Telegram message and, when you respond, injects keystrokes back into the tmux session running Claude.
| Hook | Event | What it does |
|---|---|---|
permission-hook.js |
PermissionRequest |
Sends a permission dialog with Allow / Deny / Always buttons. Blocks Claude until you respond. |
question-notify.js |
PreToolUse (AskUserQuestion) |
Sends Claude's question with selectable options. Your tap injects the selection via tmux/PTY. |
enhanced-hook-notify.js completed |
Stop |
Notifies you when Claude finishes a task, including the last response text. |
enhanced-hook-notify.js waiting |
Notification |
Notifies you when Claude is waiting for input. |
user-prompt-hook.js |
UserPromptSubmit |
Tracks terminal activity so notifications are suppressed when you're actively working. |
enhanced-hook-notify.js session-start |
SessionStart |
Notifies you when a new Claude session starts. |
enhanced-hook-notify.js session-end |
SessionEnd |
Notifies you when a Claude session ends, with the final response. |
enhanced-hook-notify.js subagent-done |
SubagentStop |
Notifies you when a subagent task completes. |
Smart suppression — all notifications (including permissions) are automatically silenced when you've sent a message to Claude within the last 5 minutes. The moment you step away, Telegram takes over. Telegram-injected commands always get their response back to Telegram regardless.
Claude requests permission
→ hook generates promptId, writes pending file
→ Telegram message with inline buttons sent to your phone
→ you tap Allow / Deny
→ bot writes response file
→ hook reads response, returns decision to Claude
→ Claude continues
Claude asks a question (AskUserQuestion)
→ Claude shows question UI in terminal
→ question-notify sends options to Telegram
→ you tap an option
→ bot injects arrow keys + Enter via tmux or PTY
→ Claude's question UI captures the keystrokes
| Command | Description |
|---|---|
/sessions |
List all active Claude sessions with status and age |
/use <workspace> |
Set default workspace — plain text messages route there |
/use |
Show current default workspace |
/use clear |
Clear the default workspace |
| Command | Description |
|---|---|
/<workspace> <command> |
Send a command to a specific Claude session |
/status [workspace] |
Show the last 20 lines of tmux pane output |
/stop [workspace] |
Send Ctrl+C to interrupt the running prompt |
/compact [workspace] |
Run /compact and wait for it to complete |
| Command | Description |
|---|---|
/new |
Show recent projects as buttons |
/new myproject |
Start Claude in ~/projects/myproject (or wherever it's found) |
The /new command searches your configured PROJECT_DIRS, finds exact or prefix-matched directories, creates a tmux session (or PTY session if tmux is unavailable), starts Claude, and sets it as the default workspace.
| Command | Description |
|---|---|
/resume |
Show projects with past Claude sessions |
/resume myproject |
Jump straight to session picker for that project |
The /resume command reads directly from Claude Code's session storage (~/.claude/projects/), giving you access to your full conversation history — not just sessions started through the bot.
Each session shows a snippet of the first message for easy identification. Sessions are sorted by last activity.
Active session protection:
- If a session appears to be running in a terminal (JSONL file modified within 5 min), you get a warning before resuming to prevent dual-instance conflicts
- If a PTY session is running, you're warned that it will be terminated (PTY sessions can't be reattached)
- tmux sessions switch seamlessly — the bot injects
/exit+claude --resumeinline, keeping your terminal connected
Prefix matching — workspace names can be abbreviated. /ass hello routes to assistant if it's unique. Ambiguous prefixes show a list to choose from.
Reply-to routing — reply to any bot notification (permission, question, or status message) to route your reply to that workspace.
Default workspace — after /use myproject, plain text messages route there automatically.
CCGram is configured via ~/.ccgram/.env. Run ccgram init to generate it interactively, or edit it manually:
# Required
TELEGRAM_ENABLED=true
TELEGRAM_BOT_TOKEN=your_bot_token_here
TELEGRAM_CHAT_ID=your_chat_id_here
# Project directories to scan (for /new command and session listing)
PROJECT_DIRS=~/projects,~/tools
# Suppress notifications when you're actively at the terminal
# Default: 300 seconds (5 minutes). Set to 0 to always notify.
ACTIVE_THRESHOLD_SECONDS=300# Allow only specific Telegram user IDs (comma-separated)
TELEGRAM_WHITELIST=123456789,987654321
# Use webhooks instead of long-polling (requires public URL)
TELEGRAM_WEBHOOK_URL=https://example.com/webhook
TELEGRAM_WEBHOOK_PORT=3001
# Force IPv4 for Telegram API (useful on some VPS providers)
TELEGRAM_FORCE_IPV4=false
# Tmux keystroke injection mode
INJECTION_MODE=tmux # tmux (default) or pty
# Custom session map path
SESSION_MAP_PATH=~/.ccgram/src/data/session-map.json
# Logging
LOG_LEVEL=info # debug, info, warn, errorccgram init generates and starts a background service automatically.
# Restart
launchctl kickstart -k gui/$(id -u)/com.ccgram
# Stop / Start
launchctl stop com.ccgram
launchctl start com.ccgram
# Logs
tail -f ~/.ccgram/logs/bot-stdout.log
tail -f ~/.ccgram/logs/bot-stderr.logsudo systemctl status ccgram
sudo systemctl restart ccgram
journalctl -u ccgram -fccgram init installs the bot to ~/.ccgram/ — a persistent directory that survives npx cleanup and system updates. The hooks in ~/.claude/settings.json always point to this location.
~/.ccgram/
├── dist/ # Compiled JavaScript (hook scripts + bot)
├── config/ # Default config templates
├── src/data/
│ ├── session-map.json # Workspace → tmux session mapping
│ ├── default-workspace.json
│ ├── project-history.json # Recent projects for /new
│ └── message-workspace-map.json # reply-to routing (24h TTL)
├── logs/
│ ├── bot-stdout.log
│ └── bot-stderr.log
└── .env # Your configuration
git clone https://github.com/jsayubi/ccgram
cd ccgram
npm install
cp .env.example .env # Add your bot token and chat ID
npm run build
node dist/workspace-telegram-bot.jsnpm run build # Compile TypeScript → dist/
npm run build:watch # Watch mode
npm test # Run 84 tests (vitest)Note: Claude Code hooks run from ~/.ccgram/dist/, not the repo's dist/. After changing hook scripts during development, sync them:
cp -r dist/ ~/.ccgram/dist/End users don't need this — ccgram init handles it automatically.
src/
├── utils/
│ ├── active-check.ts # Detect terminal activity; suppress notifications when present
│ ├── pty-session-manager.ts # Headless PTY backend via node-pty (tmux fallback)
│ ├── callback-parser.ts # Parse Telegram callback_data strings
│ ├── http-request.ts # Lightweight HTTPS wrapper (no axios)
│ ├── optional-require.ts # Graceful optional dependency loading
│ └── paths.ts # PROJECT_ROOT + CCGRAM_HOME constants
├── types/ # TypeScript interfaces
└── data/ # Runtime data (session map, history)
workspace-telegram-bot.ts # Main bot (long-polling, routing, callbacks)
workspace-router.ts # Session map, prefix matching, default workspace
prompt-bridge.ts # File-based IPC via /tmp/claude-prompts/
permission-hook.ts # Blocking permission approval hook
question-notify.ts # Non-blocking question notification hook
enhanced-hook-notify.ts # Status notification hook (Stop, Notification, SessionStart, SessionEnd, SubagentStop)
user-prompt-hook.ts # UserPromptSubmit hook — writes terminal activity timestamp
setup.ts # Interactive setup wizard
cli.ts # ccgram CLI entry point
test/
├── prompt-bridge.test.js # 15 tests — IPC write/read/update/clean/expiry
├── workspace-router.test.js # 38 tests — session map, prefix matching, defaults, reply-to, session history
├── callback-parser.test.js # 23 tests — all callback_data formats (perm, opt, new, rp, rs, rc)
└── active-check.test.js # 8 tests — terminal activity detection, thresholds
Tests use isolated temp directories and run with npm test (vitest, no configuration needed).
Core: Only dotenv is required. The bot runs on Node.js built-ins.
Optional (graceful degradation if missing):
express— webhook serversnode-pty— PTY relay modenodemailer,node-imap,mailparser— email relaypino,pino-pretty— structured logging (falls back to console)
Do I need a public server? No. CCGram uses Telegram's long-polling API — it works behind NAT, on a laptop, or anywhere with outbound HTTPS.
What if I'm already at my terminal?
All notifications — including permission requests — are suppressed automatically when you've sent a message to Claude within the last 5 minutes. The threshold is configurable via ACTIVE_THRESHOLD_SECONDS. Step away for more than 5 minutes and Telegram instantly takes over.
Can I use it with multiple projects at once?
Yes. Each Claude session maps to a named tmux or PTY session. Use /sessions to see all active sessions, or /use <workspace> to set a default for plain text routing.
Can I resume a conversation I started in the terminal?
Yes. /resume reads from Claude Code's own session storage, so it sees every conversation — not just ones started through the bot. If the session is still running in your terminal, you'll get a warning before resuming to prevent conflicts.
Do I need tmux?
No. When tmux is not detected, CCGram automatically falls back to headless PTY sessions powered by node-pty. No configuration required — it activates on its own.
To use PTY mode:
- Install the optional dependency:
npm install node-ptyinside~/.ccgram/ - PTY activates automatically when
tmuxis not running, or force it explicitly:# in ~/.ccgram/.env INJECTION_MODE=pty - Restart the bot:
launchctl kickstart -k gui/$(id -u)/com.ccgram(macOS) orsudo systemctl restart ccgram(Linux)
Full remote control — permission approvals, question answering, /new, /stop — works identically in both modes.
Is my bot token stored securely?
The token is stored in ~/.ccgram/.env, readable only by your user. It's never logged or transmitted beyond Telegram's API.
What's the 64-byte callback limit?
Telegram limits inline button callback data to 64 bytes. CCGram uses a compact type:promptId:action format to stay within this limit.
MIT — see LICENSE.
Built for developers who run Claude Code unattended — approve permissions, resume conversations, and manage AI coding agents from anywhere.