Turn your Telegram into a remote Claude Code terminal. Chat with Claude, run skills, edit code, search files — all from your phone, anywhere.
Claude Code is powerful, but it's bound to your terminal. When you step away from your computer — commuting, in a meeting, or just on the couch — you lose access. You can't quickly check a build result, ask Claude to fix a bug, or run a skill command until you're back at your desk.
Solutions like OpenClaw exist, but they come with trade-offs: a full web stack to deploy and maintain, potential security concerns with exposing your dev environment through a web interface, and heavyweight infrastructure that feels like overkill when you just want to quickly send Claude a message from your phone.
This bot takes a different approach — lightweight, zero-infrastructure, secure by default. It connects Claude Code SDK to a Telegram bot (a messaging app you already have), so you get a persistent, always-on Claude Code session you can talk to from anywhere. No web server, no ports to expose, no extra auth layer. Start it once for a project directory, and it runs as a daemon in the background — surviving crashes, rebooting with your Mac, managing its own dependencies. Telegram itself handles authentication, encryption, and push notifications.
![]() |
![]() |
![]() |
Core
- Chat with Claude directly in Telegram, powered by Claude Code SDK
- Invoke any Claude Code skill (
/skill <name>) or slash command (/command <cmd>) remotely - Switch between Sonnet, Opus, and Haiku on the fly via
/model - Resume previous conversations with
/resumeand browse session history - View recent conversation history with
/history— displays last 5 messages from current session - Revert to any previous message with
/revert— choose from 5 modes: full restore (code + conversation), conversation only, code only, summarize from point, or cancel
Smart Interaction
- Progressive streaming: AI responses update in real-time as Claude thinks, not after completion
- Claude's numbered options auto-convert to Telegram inline keyboard buttons — just tap to choose
- File paths in Claude's responses are automatically sent as photos or documents — covers document, spreadsheet, presentation, data, archive, and audio/video/image types (source-code files are not auto-sent, to avoid pushing every edited file during coding). Files up to 50 MB; those under
PROJECT_ROOTsend automatically, and files outside it ask for a one-tap confirmation before sending instead of being dropped - Inbound Telegram documents are downloaded into a private project-scoped temporary path and passed to the active agent runtime for inspection
- Native Telegram voice messages: auto-download, format detection/conversion (OGG/AMR → MP3), Whisper transcription, then forwarded to Claude
- Per-user dedicated SDK streams — low latency, concurrent message support (up to 3 per user)
- Priority
/stopcommand: immediately cancels running tasks and voice transcription, even when message queue is full - Priority
/revertcommand: bypasses message queue limit, cancels active operations, restores conversation state to any previous point
Security
- File access inside the project directory is auto-allowed
- Access outside the project triggers inline-button confirmation in Telegram
- User whitelist via
ALLOWED_USER_IDS - Stale messages (>20 min) are silently dropped
Operations
- Daemon mode with auto-restart on crash (stops after 5 rapid crashes in 60s)
- One-command macOS launchd auto-start on boot (
--install) with inheritedPATH/HOME - Auto-update check on startup — notifies when new version available
- One-command upgrade (
--upgrade) — pulls latest code and reinstalls dependencies - MD5-based dependency caching — skips reinstall when
requirements.txtis unchanged - Auto venv creation, 14-day log rotation, crash logging with exit codes
- Dedicated polling HTTP client with proxy-aware HTTP/1.1 settings for better recovery after network changes
- Python 3.11+
- Provider CLI — Claude CLI (default), or Codex CLI when
CCC_AGENT_PROVIDER=codex - Codex authentication — for Codex, complete the CLI login flow and pass
../scripts/ccc-doctor.shbefore starting the bridge - Telegram Bot Token — from @BotFather
- ffmpeg — required for audio format conversion
- OpenAI API Key — required for Whisper transcription (
OPENAI_API_KEY)
- macOS — fully supported, including launchd auto-start via
--install/--uninstall - Linux (systemd) — supported, including reboot-persistent auto-start via
--install-systemd/--uninstall-systemd - WSL (Ubuntu/Debian-style Linux userland) — supported for foreground run,
--daemon,--status, and--stop - Native Windows (PowerShell / CMD) — not supported
Install rust, rust-std-aarch64-linux-android, and patchelf with pkg
when provisioning a Termux bridge. Python source builds can produce a
cryptography extension without its libpython dependency. At every start,
including a requirements-cache hit, bootstrap imports the native extension
and the Agent SDK. A failed native/SDK import stops startup by default.
For the known Android PyLong_Type/PyExc_* link failure, bootstrap uses
patchelf to add the current interpreter's shared-library dependency to the
venv's extension, validates the candidate with that interpreter, then replaces
it atomically. Concurrent bootstrap installs and repairs serialize on
venv/.termux-native.lock, including distinct bridge projects sharing a venv.
Do not run an independent pip installer against that venv during bootstrap.
Originals are retained in a private .ccc-native-recovery-* directory beside
the extension. No pinned package version or download hash is changed. A
subsequent package reinstall is checked and repaired again; a missing
patchelf produces an explicit installation instruction.
CCC_DEPS_SMOKE_IMPORTS overrides the general import list, and
CCC_DEPS_SMOKE_STRICT=0 makes that diagnostic warn-only. Neither bypasses the
Termux cryptography repair gate. Do not use those overrides to hide a broken
startup dependency.
For a new isolated runtime when pip attempts to compile maturin itself, use Termux build preparation. It prepares locked native packages with the existing distro backend, verifies a new venv, and can exercise a real forced reinstall before any service transition.
git clone https://github.com/jinwon-int/ccc-node
cd ccc-node/bridge
./setup.shStart the bot after setup:
./start.sh --path /path/to/your/projectThe installed runtime identity comes from the canonical ccc-node checkout, not
from the historical bridge changelog or upstream releases. Use
./start.sh --version to read it and ./start.sh --upgrade to invoke the
reviewed scripts/ccc-self-update.sh path. See
Version and provenance.
./start.sh --path /path/to/project # Start (foreground)
./start.sh --path /path/to/project -d # Start (daemon/background)
./start.sh --path /path/to/project --debug # Debug mode
./start.sh --path /path/to/project --status # Check status
./start.sh --path /path/to/project --stop # Stop
./start.sh --path /path/to/project --restart # External shell only; refuses in-bridge self-restart
./start.sh --path /path/to/project --upgrade # Update to latest version
./start.sh --path /path/to/project --install # macOS only: install startup service
./start.sh --path /path/to/project --uninstall # macOS only: remove startup service
./start.sh --path /path/to/project --install-systemd # Linux: install systemd startup service (reboot-persistent)
./start.sh --path /path/to/project --uninstall-systemd # Linux: remove systemd startup serviceLinux reboot-persistence.
--installis macOS/launchd only. On Linux use--install-systemd: run as root it writes a system unit to/etc/systemd/system/ccc-telegram-bridge.serviceand runssystemctl enable --now; run as a normal user it installs asystemctl --userunit under~/.config/systemd/user. systemd supervises the bridge in the foreground withRestart=always, so a direct signal that the bridge handles as a clean exit is recovered; an explicitsystemctl stopremains stopped. Do not combine systemd with-d. Override the unit name withBRIDGE_SERVICE_NAME=...to run multiple bridges on one host. Subsequent top-levelsetup.sh/self-update runs compare an existing ccc-generated main unit with this canonical renderer and atomically reconcile drift followed bydaemon-reload; they do not restart the bridge or change its enabled/active state. Put node-local systemd settings in/etc/systemd/system/ccc-telegram-bridge.service.d/*.conf(or the corresponding~/.config/systemd/user/...service.d/directory), because bespoke main units are deliberately left untouched for explicit operator normalization.
Restart ownership. Run
--restartfrom a shell outside the serving bridge process tree. An agent turn that tries to restart its own bridge is refused before stop (exit 5), because stopping the ancestor would also kill the restart driver. For systemd installations usesystemctl restart ccc-telegram-bridge.service.
You're away from your desk and a teammate reports a bug. Open Telegram:
You: login page crashes when email contains a plus sign
Claude: I found the issue in src/auth/validator.ts:42 — the regex
doesn't escape the + character. Fixed and the test passes now.
You: /skill commit
Claude: Created commit: fix(auth): escape special characters in email validation
You: [send Telegram voice message]
Bot: 🎤 Voice: summarize yesterday's git diff
Claude: Here is a summary of yesterday's changes...
You: /resume
Bot: 1. Refactoring auth module — 2 hours ago
2. Adding dark mode — yesterday
3. API rate limiting — 3 days ago
You: 1
Claude: Resuming session... [continues from where you left off]
You: /history
Bot: 📜 Recent History (last 5 messages)
🧑 User [2026-03-05 14:23:15]
fix the login bug
🤖 Assistant [2026-03-05 14:23:18]
I found the issue in src/auth/validator.ts:42...
🧑 User [2026-03-05 14:25:30]
add a test for this
🤖 Assistant [2026-03-05 14:25:35]
Added test in tests/auth.test.ts...
You: /revert
Bot: 🔄 Select a message to revert to:
[Shows paginated list of last 50 messages with inline buttons]
You: [Tap on a message]
Bot: Choose revert mode:
1️⃣ Restore code and conversation
2️⃣ Restore conversation only
3️⃣ Restore code only
4️⃣ Summarize from here
5️⃣ Cancel
You: [Tap "Restore code and conversation"]
Bot: ✅ Reverted to message #42. Conversation and code state restored.
You: /model haiku
Bot: Switched to Claude Haiku
You: summarize the changes in src/api/ from the last 3 commits
Claude: ...
When CCC_AGENT_PROVIDER=codex, /effort shows only the effort values
advertised for the selected model by Codex model/list. The override is stored
per Telegram conversation and is sent on each turn/start.
You: /effort
Bot: Select reasoning effort for GPT-5.3-Codex...
You: /effort high
Bot: Reasoning effort set to high
You: /effort default
Bot: Reasoning effort reset to model default
Changing /model preserves the current effort only when the new model advertises
it as supported; otherwise the override is removed and the bot reports the reset.
/effort is unavailable while the Claude provider is active.
# Install as macOS startup service — survives reboots
./start.sh --path ~/my-project --install
# The generated launchd plist preserves PATH and HOME
# so Claude CLI and proxy settings still resolve at boot
# Check status anytime
./start.sh --path ~/my-project --status
# 🟢 Bot is running (PID: 12345)
# Uninstall when done
./start.sh --path ~/my-project --uninstall| Command | Description |
|---|---|
/start |
Start a conversation |
/new |
Start a new session (clears current stream and cancels ongoing streaming) |
/model |
Switch model (provider-specific list or /model <id>) |
/usage |
View read-only provider rate limits and current-session usage without starting a turn |
/effort [value|default] |
Select or reset per-conversation Codex reasoning effort |
/resume |
Browse and resume a previous session (shows progress summary with last assistant message) |
/stop |
Interrupt execution immediately (bypasses queue, cancels active task) |
/restart |
Safely restart an opted-in Linux systemd bridge (sole owner, private chat only) |
/history |
View recent conversation history |
/revert |
Revert to a previous conversation state (browse history, select message, choose restore mode) |
/skills |
List available Claude Code skills |
/skill <name> [args] |
Execute a skill command |
/command <cmd> [args] |
Execute a Claude Code slash command |
Any unrecognized direct slash command is also forwarded as a skill invocation.
On an owner-operated, audience-scoped Claude bridge, host setting sources stay
disabled; the bridge selectively resolves an explicitly invoked, safety-checked
local SKILL.md instead of exposing host hooks or unscoped memory settings.
/usage uses Codex app-server read methods and already-observed token updates.
For Claude it uses only existing Agent SDK result metadata plus an optional,
sanitized status-line snapshot. Codex output omits the
GPT-5.3-Codex-Spark bucket and account lifetime/daily history, suppresses
empty context/session-token rows, and renders reset timestamps in KST. Claude
continues to report missing data as unavailable. The command never launches
a provider turn or reads transcript/credential files.
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
Yes | — | Telegram Bot API token |
ALLOWED_USER_IDS |
No | (allow all) | Comma-separated user ID whitelist; owner-operator requires exactly one owner |
CCC_REQUIRE_ALLOWLIST |
No | true |
Refuse startup when the allowlist is empty; must stay true for owner-operator |
CCC_BRIDGE_RESTART_HANDOFF |
No | off |
Set systemd to enable sole-owner DM /restart through an external transient unit |
CCC_BRIDGE_RESTART_UNIT |
No | ccc-telegram-bridge.service |
Exact ccc-telegram-bridge*.service target allowlist |
CCC_BRIDGE_RESTART_DELAY_SECONDS |
No | 5 |
Delay (5–30s) before the external worker restarts the bridge |
CCC_BRIDGE_EXECUTION_PROFILE |
No | strict-project |
Execution boundary: strict-project, owner-operator, or disabled |
CCC_BRIDGE_BASH_POLICY |
No | auto-approve |
Bash approval UX; Codex default is unrestricted never + dangerFullAccess |
CCC_AGENT_PROVIDER |
No | claude |
Main runtime provider: claude, codex, piri, or crush |
CCC_CODEX_CLI_PATH |
Codex only | ~/.claude/hooks/ccc-codex |
Installed memory-bootstrap launcher used for direct/app-server runs |
CCC_CODEX_REAL_CLI_PATH |
Codex only | codex |
Underlying Codex binary invoked by the launcher |
CCC_CODEX_MEMORY_MATERIALIZER_PATH |
Codex only | ~/.claude/hooks/ccc_codex_memory.py |
Body-free materialize/status command run at thread boundaries |
CCC_CODEX_MEMORY_BOOTSTRAP_TIMEOUT_SEC |
Codex only | 14 |
Per-command materializer timeout |
CCC_CODEX_DISTILL_CHECKPOINT_TURNS |
Codex only | 0 |
Completed-turn checkpoint gate; 0 disables |
CCC_CODEX_DISTILL_CHECKPOINT_BYTES |
Codex only | 0 |
UTF-8 user + assistant byte checkpoint gate; 0 disables |
CCC_CODEX_DISTILL_CHECKPOINT_AGE_SECONDS |
Codex only | 0 |
Runtime-age checkpoint gate, evaluated after a completed turn; 0 disables |
CCC_MEMORY_DISTILL_PROVIDER |
Claude/Codex/Piri | auto |
Extractor backend: auto follows CCC_AGENT_PROVIDER; explicit claude/codex/piri overrides; off disables shared distill workers |
CCC_MEMORY_DISTILL_MODEL |
Claude/Codex/Piri | provider-default |
Isolated extractor model; provider-default follows the selected runtime configuration and accepts provider-qualified Piri model IDs |
CCC_MEMORY_DISTILL_TIMEOUT_SEC |
Claude/Codex/Piri | 120 |
Per-attempt provider-neutral extraction timeout, bounded to 1–600 seconds |
CCC_MEMORY_DISTILL_ALLOW_UNBOUNDED |
Claude/Codex/Piri | false |
Explicit escape hatch for extraction without a finite autonomous provider budget; default fails closed |
CCC_MEMORY_DISTILL_MAX_ATTEMPTS |
Claude/Codex/Piri | 5 |
Maximum extraction attempts retained per journal job |
CCC_MEMORY_DISTILL_RETRY_BACKOFF_BASE_SEC |
Claude/Codex/Piri | 300 |
Initial durable retry-after; subsequent retry delays double |
CCC_MEMORY_DISTILL_RETRY_BACKOFF_MAX_SEC |
Claude/Codex/Piri | 21600 |
Maximum durable extraction retry delay |
CCC_MEMORY_DISTILL_PROVIDER_COOLDOWN_SEC |
Claude/Codex/Piri | 3600 |
Provider+model cooldown after auth, quota, rate-limit, or model availability failure |
CCC_MEMORY_DISTILL_MAX_JOBS_PER_SWEEP |
Claude/Codex/Piri | 1 |
Maximum extraction jobs attempted during one scheduler sweep |
CCC_MEMORY_DISTILL_CHECKPOINT_TURNS |
Claude/Codex/Piri | 0 |
Completed-turn checkpoint gate for the shared journal; 0 disables |
CCC_MEMORY_DISTILL_CHECKPOINT_BYTES |
Claude/Codex/Piri | 0 |
UTF-8 checkpoint byte gate for the shared journal; 0 disables |
CCC_MEMORY_DISTILL_CHECKPOINT_AGE_SECONDS |
Claude/Codex/Piri | 0 |
Runtime-age checkpoint gate for the shared journal; 0 disables |
CCC_CODEX_DISTILL_MODEL |
Codex only | provider-default |
Isolated write-back extractor model; a safe non-default ID is passed explicitly with --model |
CCC_CODEX_DISTILL_TIMEOUT_SEC |
Codex only | 120 |
Per-attempt extraction timeout, bounded to 1–600 seconds |
CCC_CODEX_AUDIENCE_AUTH_MODE |
Scoped Codex only | disabled |
Set to keyring only after Codex credentials are provisioned in the OS keyring; file credentials are never copied |
CCC_RESUME_PERSISTED_SESSIONS |
Claude only | true |
Resume a persisted session after restart when its SDK transcript still exists; required by dead-session wakeup |
CCC_DEAD_SESSION_WAKEUP |
Claude only | false |
Wake an exited session to deliver pending background-task notifications instead of leaving them orphaned until the next manual message. Enabling starts token-consuming autonomous turns, metered as autonomous in usage-meter.json and gated by the daily Claude autonomous allowance; skip totals are visible in health/status; requires CCC_RESUME_PERSISTED_SESSIONS=true |
CCC_USAGE_METER_ENABLED |
No | true |
Write body-free usage counters by provider and interactive/autonomous mode to .telegram_bot/usage-meter.json |
CCC_USAGE_BUDGET_TOKENS_CLAUDE |
Claude only | 0 |
Daily Claude autonomous input+output token allowance; 0 disables the gate. Interactive usage remains metered but does not consume the allowance and is never rejected |
CCC_USAGE_BUDGET_TOKENS_CODEX |
Codex only | 0 |
Daily Codex autonomous input+output token allowance; 0 disables the gate. Interactive usage remains metered but does not consume the allowance and is never rejected |
CCC_USAGE_BUDGET_WARN_PERCENT |
No | 80 |
Early-alarm percentage for a configured daily token budget |
CLAUDE_CLI_PATH |
No | (auto-detect) | Absolute path to Claude CLI binary |
CLAUDE_SETTINGS_PATH |
No | ~/.claude/settings.json |
Path to Claude Code settings file |
CLAUDE_PROCESS_TIMEOUT |
No | 21600 |
Whole-turn SDK timeout in seconds (6 hours; snapshotted at process start) |
CCC_CLAUDE_MAX_BUFFER_SIZE |
No | 16777216 |
Max bytes for one Claude Agent SDK stdout NDJSON line (16 MiB; accepted range 1 MiB–256 MiB). Always passed explicitly — the SDK's unset default is 1 MiB, and one oversized line (e.g. a screenshot whose base64 the CLI duplicates across two fields) kills the message reader and the whole turn |
CCC_DELEGATED_TASK_STALL_SECONDS |
No | 7200 |
Hard limit for the oldest delegated task; must be lower than CLAUDE_PROCESS_TIMEOUT |
CCC_APPROVAL_STALL_SECONDS |
No | 120 |
Fail an admitted turn whose provider approval remains pending for this many wall-clock seconds; 0 disables this guard |
CCC_MAX_DOCUMENT_SIZE_MB |
No | 10 |
Maximum inbound Telegram document size in decimal MB (1–20) |
AUTO_NEW_SESSION_AFTER_HOURS |
No | 24 |
Auto-start new session after N hours of inactivity; set to 0/false/off to disable |
CCC_BRIDGE_SESSION_GUARD_ENABLED |
No | true |
Bound resident provider sessions without interrupting active requests |
CCC_BRIDGE_BUSY_NOTICE_ENABLED |
No | true |
Include the elapsed-time busy acknowledgement while a turn is active; durable queue acceptance/rejection receipts remain visible when disabled |
CCC_BRIDGE_BUSY_NOTICE_MIN_ELAPSED_SECONDS |
No | 10 |
Minimum active-turn age before sending the acknowledgement |
CCC_BRIDGE_FOLLOWUP_QUEUE_CAP |
No | 32 |
Maximum restart-safe FIFO follow-ups per conversation; excess messages are explicitly rejected and never silently dropped |
CCC_BRIDGE_FOLLOWUP_FAILURE_NOTIFICATION_CAP |
No | 32 |
Separate cap for retained discard receipts per conversation; at the cap, the newest failed item remains queued and processing pauses instead of evicting an older receipt |
CCC_BRIDGE_FOLLOWUP_RETRY_BACKOFF_SECONDS |
No | 1,5,30 |
Increasing wall-clock delays for durable follow-up dispatch and discard-notification retries |
CCC_BRIDGE_FOLLOWUP_WORKER_RESTART_CAP |
No | 3 |
Consecutive supervised worker restarts allowed per conversation before that worker is disabled |
CCC_BRIDGE_FOLLOWUP_WORKER_RESTART_BACKOFF_SECONDS |
No | 1 |
Initial delay for exponential worker-restart backoff |
CCC_BRIDGE_SESSION_GUARD_INTERVAL_SECONDS |
No | 60 |
Idle resource-guard sweep interval (10–3600s) |
CCC_BRIDGE_SESSION_IDLE_TTL_SECONDS |
No | 14400 |
Close a local provider runtime after this idle period; durable session IDs remain resumable |
CCC_BRIDGE_MAX_RESIDENT_SESSIONS |
No | 2 |
LRU cap for cached sessions; active sessions are protected |
CCC_BRIDGE_SESSION_TREE_RSS_LIMIT_MB |
No | 1024 |
Idle bridge-tree RSS high-water mark; 0 disables |
CCC_BRIDGE_CODEX_MAX_ATTACHMENTS |
Codex only | 2 |
Recycle the idle app-server before a third conversation attachment; 0 disables |
DRAFT_UPDATE_MIN_CHARS |
No | 150 |
Minimum characters before streaming draft update |
DRAFT_UPDATE_INTERVAL |
No | 1.0 |
Minimum seconds between streaming draft updates |
ENABLE_STREAMING_TOOL_CALLS |
No | false |
Show Claude tool calls in Telegram streaming messages |
TRANSCRIPTION_PROVIDER |
No | whisper |
Voice transcription provider: whisper or volcengine |
OPENAI_API_KEY |
Voice only | — | OpenAI API key for Whisper transcription |
OPENAI_BASE_URL |
No | (official OpenAI API) | OpenAI-compatible Whisper endpoint base URL |
WHISPER_MODEL |
No | whisper-1 |
Whisper model name |
VOLCENGINE_APP_ID |
Volcengine only | — | Volcengine ASR X-Api-App-Key |
VOLCENGINE_TOKEN |
Volcengine only | — | Volcengine ASR X-Api-Access-Key |
VOLCENGINE_ACCESS_KEY |
Volcengine only | — | Volcengine TOS Access Key |
VOLCENGINE_SECRET_ACCESS_KEY |
Volcengine only | — | Volcengine TOS Secret Access Key (create at https://console.volcengine.com/iam/keymanage) |
VOLCENGINE_TOS_BUCKET_NAME |
Volcengine only | — | TOS bucket used for staging Telegram voice files |
VOLCENGINE_TOS_ENDPOINT |
Volcengine only | — | TOS endpoint (must match your bucket region, e.g. https://tos-cn-shanghai.volces.com) |
VOLCENGINE_TOS_REGION |
No | cn-beijing |
TOS region used by SDK signing |
FFMPEG_PATH |
No | (auto-detect) | Absolute path to ffmpeg binary |
VOICE_REPLY_PERSONA |
No | Tingting |
Persona name used by voice reply mode |
LOG_LEVEL |
No | INFO |
Logging level |
PROXY_URL |
No | — | HTTP proxy; auto-configures http_proxy/https_proxy/all_proxy |
- Non-image Telegram documents are accepted after the normal allowlist and declared-size checks.
- All file types are accepted, including executable binaries: the sender allowlist is the trust boundary. Uploads are stored non-executable (
0600) and are never run by the bridge; agent-side execution stays gated by the Bash tool policy. - Telegram's returned file metadata is rechecked before storage. Each upload is created relative to a validated owner-owned
0700directory fd, with a random server-side name,O_EXCL/O_NOFOLLOW, and validated regular-file0600permissions. - Actual writes are bounded by
CCC_MAX_DOCUMENT_SIZE_MB(default: 10 decimal MB, range: 1–20). - The local path, sanitized display name, MIME type, size, and optional caption are passed to the active agent runtime. Unsupported runtime formats are reported explicitly.
- Temporary files are removed after success, failure, or cancellation; startup pruning touches only regular bridge-generated artifacts and does not follow symlinks.
- Default channel is
whisper. - To use Volcengine file-fast ASR, set:
TRANSCRIPTION_PROVIDER=volcengineVOLCENGINE_APP_IDVOLCENGINE_TOKENVOLCENGINE_ACCESS_KEYVOLCENGINE_SECRET_ACCESS_KEYVOLCENGINE_TOS_BUCKET_NAMEVOLCENGINE_TOS_ENDPOINT
- Secret Access Key must be created in Volcengine IAM key management:
https://console.volcengine.com/iam/keymanage
- In Volcengine mode, the bot now uses
download -> TOS upload -> signed TOS URL -> ASR.
- User voice messages switch reply mode to voice automatically.
- User text messages switch reply mode back to text.
- In voice mode:
>1000Chinese characters or>1000English words: text only (voice mode is kept)>300characters (and not over 1000 threshold): send voice + text- Otherwise: send voice only
- Voice transcription preview (
🎤 Voice: ...) is bundled with the final reply:- if text is sent, preview is merged at the top of that same text message
- if voice-only reply is sent, the bot sends preview text first, then voice
- The bot uses macOS
sayfor synthesis, then converts output to Telegram-compatibleogg/opusviaffmpeg. VOICE_REPLY_PERSONAshould be a real macOS voice name fromsay -v ?.- If
VOICE_REPLY_PERSONAis unavailable on current system, the bot sends a friendly error and falls back to text for that reply. - Set
VOICE_REPLY_PERSONAto the first-column name shown bysay -v ?. - Example:
VOICE_REPLY_PERSONA=Tingting
Install ffmpeg before enabling voice messages:
- macOS (Homebrew):
brew install ffmpeg - Ubuntu/Debian or WSL:
sudo apt-get update && sudo apt-get install -y ffmpeg
Then verify:
ffmpeg -versionVoice transcription uses OpenAI Whisper API (whisper-1) and incurs usage-based charges.
Current reference pricing is about $0.006/minute of audio. Check OpenAI pricing for latest values.
--pathsets thePROJECT_ROOTworking directory and structured-tool approval boundary.- Structured file tools (
Read,Edit,Write,MultiEdit,Glob,Grep) keep their existing UX policy: paths insidePROJECT_ROOTare auto-allowed and outside paths require user confirmation. CCC_BRIDGE_EXECUTION_PROFILEselects the execution boundary independently from Bash approval:strict-project(package default) preserves the fail-closed Claude Code OS sandbox: host reads are denied by default, onlyPROJECT_ROOTplus the minimal runtime is re-allowed, unsandboxed fallback/excluded commands are disabled, and user/project/local settings are suppressed. Linux/WSL2 requiresbubblewrapandsocat; unsupported/unavailable sandbox backends fail closed.owner-operatordeliberately runs without the OS sandbox and restores normal user/project/local settings plus host-capable Claude Code utility. It starts only withCCC_REQUIRE_ALLOWLIST=trueand exactly oneALLOWED_USER_IDSowner. This profile trusts that owner boundary and is not a prompt-injection defense.disabledhard-denies Bash and suppresses user/project/local filesystem settings so settings hooks cannot retain host execution. Unknown or unsafe profile values also resolve to disabled.
CCC_BRIDGE_BASH_POLICYcontrols approval UX:auto-approve(default),auto-review,approve-each, ordisabled. Claude approval never widens its selected execution profile. Codex is different: its defaultauto-approvepolicy intentionally requests full access regardless ofstrict-projectvsowner-operator. Claude treatsauto-reviewconservatively likeapprove-eachbecause Claude has no Codex reviewer. Codex mapsapprove-eachto app-serverapprovalPolicy=untrusted: trusted commands such asls,cat, andsedrun without a prompt, while untrusted actions use the owner-only Telegram approval UI.- Claude approval authority is bound to one exact live run generation. Delegated local agent/workflow continuations keep that route across intermediate SDK result frames; completion, interruption, session/provider replacement,
/new, and teardown revoke it, and a stale callback is never rebound to the next turn. The external-wait CLI uses a separate file-backed active-turn publication contract; approval revocation does not rewrite that route. - Codex
auto-approve(the package default) sendsapprovalPolicy=never, no reviewer, andsandboxPolicy={type: dangerFullAccess}on every turn. It provides unrestricted external/Tailscale network, host filesystem, systemd, SSH, device, and out-of-workspace access without an approval prompt. When the bridge runs as root, every allowlisted Telegram user is part of the root trust boundary; prefer exactly one trusted owner. This mode is not sandboxed and is not a prompt-injection defense. - Codex
auto-reviewsendsapprovalPolicy=on-request,approvalsReviewer=auto_review, andsandboxPolicy={type: workspaceWrite, networkAccess: false}on every turn. Routine workspace work proceeds without a prompt; eligible filesystem or network boundary crossings are evaluated by Codex's reviewer agent. Auto-review does not widen the sandbox, automatically grant network access, or provide a security guarantee. Reviewer denials instruct the main agent to find a materially safer path or stop and ask the user. - Codex provider approvals that still reach Telegram are delivered only to the single allowlisted owner. They are turn-scoped and offer Allow or Deny only; Codex has no session-wide Allow All path. The owner may tap a button or send an exact standalone reply such as
승인,허용,진행,approve,allow,거절,취소, ordeny. Ordinary sentences and ambiguous multiple-pending cases never allow.auto-approvemaps to Codexnever; disabled policy requests are denied without rendering approval buttons. Because Codexuntrustedis not a deny-all mode, trusted read commands can still run when the Bash policy isdisabled; do not treat the Codex approval UX as a command-disable control. - Claude and Codex
approve-eachprompts share a provider-neutral snapshot: the owner sees a bounded redacted target summary, optional working-directory hint, and fixed risk hints. The one-shot token is bound to keyed request and exact-display fingerprints, so changed arguments invalidate the old approval even when a provider reuses its request id. Body-free asked/terminal records are stored underBOT_DATA_DIR/approval-auditwith strict owner-only modes; seedocs/approval-audit.md. - Bot output referencing external files requires confirmation before sending.
- All runtime data stays under
PROJECT_ROOT/.telegram_bot/.
Codex rollout is source/config driven and must be serial:
- Install Codex CLI, authenticate it using the CLI's normal login flow, and run repository
setup.shsoccc-codexplusccc_codex_memory.pyare installed under the harness hooks directory. - Set
CCC_AGENT_PROVIDER=codex. KeepCCC_CODEX_CLI_PATHon the installedccc-codexwrapper and setCCC_CODEX_REAL_CLI_PATHonly when the real binary is not found ascodex. The package-defaultauto-approvepolicy isnever + dangerFullAccess; use an explicit non-default Bash policy if unrestricted host/network access is not intended. - Run
../scripts/ccc-memory-check.sh --json, require.codex.status == "ready", and inspect.writeback_queuefordegradedstate before running../scripts/ccc-doctor.shand requiringreadiness: ready. These diagnostics are body-free and do not start a model turn, initialize a missing queue, or poll Telegram. - Stop the existing bridge owner, then start exactly one replacement and verify status. Two services must never poll the same Telegram bot token concurrently.
Roll back by stopping the Codex bridge, restoring CCC_AGENT_PROVIDER=claude, and starting the prior Claude bridge as the sole poller. Do not overlap old and new services during rollout or rollback.
./start.sh --path /path/to/project --status # Check if running
./start.sh --path /path/to/project --stop # Stop
./start.sh --path /path/to/project --install # macOS only: launchd auto-start on boot
./start.sh --path /path/to/project --uninstall # macOS only: remove auto-startThe daemon auto-restarts on crash, logs each crash with exit code and uptime, and stops restarting after 5 rapid crashes in 60 seconds.
When PROXY_URL is set, both regular Bot API calls and long-polling requests use proxy-aware HTTP/1.1 clients. This helps the bot recover more reliably after laptop sleep, network handoffs, or proxy reconnects.
If your project directory is located in ~/Documents, ~/Desktop, or ~/Downloads, macOS privacy protection will block launchd from reading files in those directories. This causes --install to fail with exit code 78 (EX_CONFIG).
Solution: Add /bin/bash to Full Disk Access:
- Open System Settings → Privacy & Security → Full Disk Access
- Click the + button
- Press
Cmd + Shift + Gand type/bin/bash - Select
bashand confirm
After this, --install will work correctly in protected directories.
./start.sh --path /path/to/project --debug
# Or: BOT_DEBUG=1 python -m telegram_bot --path .Enables full console logging, per-session chat logs, and SDK tool call tracing.
MIT


