A Telegram bot that gives you remote access to @openai/codex through a Node.js runtime with two Codex backends: the Codex SDK and the legacy CLI/PTy path.
It is strictly inspired by RichardAtCT/claude-code-telegram, but this project is implemented for Codex SDK/CLI + MCP + Subagent routing.
This bot connects Telegram to Codex and routes tasks to the right execution surface:
- Coding tasks -> Codex SDK threads or Codex CLI/PTy sessions
- Explicit tool tasks -> Subagents (
/mcp,GitHub Skill) - Proactive automation -> Cron scheduler for daily summaries and push notifications
Key design goals:
- Keep Codex interactive sessions smooth and stream-safe on Telegram
- Enforce zero-trust access with whitelist-only users
- Avoid duplicate MCP calls by separating Codex MCP vs Bot MCP responsibilities
- Prefer the SDK backend for new installs, while keeping the CLI backend as a fallback
- Node.js 20+ -- https://nodejs.org/en/download/current
- Codex CLI -- https://github.com/openai/codex
- Telegram Bot Token -- from
@BotFather
git clone https://github.com/MackDing/codex-telegram-claws.git
cd codex-telegram-claws
npm installcp .env.example .envMinimum required:
BOT_TOKEN=123456789:telegram-token
ALLOWED_USER_IDS=123456789
STATE_FILE=.codex-telegram-claws-state.json
WORKSPACE_ROOT=.
CODEX_WORKDIR=.
CODEX_BACKEND=sdkOptional safe shell:
SHELL_ENABLED=true
SHELL_READ_ONLY=true
SHELL_ALLOWED_COMMANDS=["pwd","ls","git status","git diff --stat","npm test","npm run check"]
SHELL_DANGEROUS_COMMANDS=["git add","git commit","git push","rm","mv","cp","npm publish"]npm run startDevelopment mode:
npm run devValidation:
npm run check
npm run lint
npm run format:check
npm test
npm run healthcheck
npm run healthcheck:livenpm run start- start the botnpm run dev- watch mode for local developmentnpm run check- TypeScript type and syntax validation for the repositorynpm run typecheck- run the TypeScript compiler in--noEmitmodenpm run lint- ESLint for source, tests, scripts, and local JS/CJS config filesnpm run lint:fix- apply safe lint fixesnpm run format- format repository files with Prettiernpm run format:check- verify formattingnpm test- run the full test suitenpm run healthcheck- static runtime readiness checknpm run healthcheck:strict- stricter production-oriented health checknpm run healthcheck:live- live Codex + Telegram probe against the configured backend and bot tokennpm run telegram:smoke- live Telegram API smoke test when a real bot token is available
Telegram Message
-> src/bot/handlers.ts
-> src/orchestrator/router.ts
-> src/runner/ptyManager.ts (coding tasks -> Codex SDK or Codex CLI)
-> src/orchestrator/skills/*.ts (general tasks -> MCP/GitHub subagents)
-> src/bot/formatter.ts
-> Telegram sendMessage/editMessageText
Core modules:
src/index.ts: bootstrap and lifecyclesrc/config.ts: env parsing and validationsrc/bot/: auth middleware, formatting, command handlerssrc/orchestrator/: routing + MCP client + skillssrc/runner/ptyManager.ts: Codex runner abstraction for SDK threads, CLI/PTy sessions, and CLI exec fallbacksrc/cron/scheduler.ts: proactive scheduled push
Enterprise target architecture: docs/enterprise-architecture.md Enterprise Phase 1 roadmap: docs/phase-1-roadmap.md
To avoid duplicated context fetch:
- Coding requests are sent directly to Codex (SDK or CLI backend; Codex can use its own MCP stack)
- Bot-side MCP is only used by explicit
/mcp ...commands
This prevents:
- duplicate queries against the same MCP server
- extra latency/token/tool cost
- context drift from two independent MCP execution surfaces
In this repository, "subagent" means a dedicated skill executor behind the router, not a second free-form Codex session.
Current subagents:
githubskill - local git actions, repo creation through GitHub API, and test job trackingmcpskill - explicit MCP server inspection, enable/disable, tool listing, and tool calls
How they are triggered:
- Explicit commands always go straight to the matching subagent:
/gh ...-> GitHub skill/mcp ...-> MCP skill
- Plain text may also route to a subagent when the router sees a supported GitHub-style request such as
git push,commit, orrun test - Everything else falls back to Codex
Where this happens:
- Router decision order: router.ts
- Skill toggles per chat: skillRegistry.ts
- Telegram command entrypoints: handlers.ts
Operationally, subagents are the bot's control plane. Codex remains the coding execution plane.
General:
/start- bootstrap message/help- command summary/status- show current chat status, active runner mode, workdir, model override, MCP servers/pwd- show the current project directory for this chat/repo- list switchable git projects underWORKSPACE_ROOT/repo <name>- switch the current chat to another project/repo <keyword>- fuzzy match projects; switch if only one match, otherwise list candidates/repo <typo>- suggests the closest project name when there is no direct match/repo recent- show recent projects for the current chat/repo -- switch back to the previous project/new- clear the saved Codex conversation for the current project and start fresh on the next message/exec <task>- force a one-off Codex run without saving project context/auto <task>- force a one-off fully automatic Codex run without saving project context/plan <task>- ask Codex for a plan only, without direct file modification intent/continue- replay the last blocked same-workdir Codex request once/model [name|reset]- show or set the model override for the current chat/language [en|zh|zh-HK]- show or set the system language for the current chat/verbose [on|off]- show or toggle system notices for the current chat/skill list- show skill switches for the current chat/skill status- alias of/skill list/skill on <name>- enable a skill for the current chat/skill off <name>- disable a skill for the current chat/sh <command>- run a safe allowlisted Linux command in the current project (disabled by default)/sh --confirm <command>- confirm a dangerous command when writable mode is enabled/restart- restart the bot process explicitly from Telegram/interrupt- interrupt the active Codex run/stop- terminate the active Codex run/cron_now- trigger daily summary immediately
MCP skill:
/mcp list/mcp status [server]/mcp reconnect <server>/mcp enable <server>/mcp disable <server>/mcp tools <server>/mcp call <server> <tool> {"query":"..."}
GitHub skill:
/gh commit "feat: message"->git add .+ commit + push/gh push-> push current branch/gh create repo my-new-repo-> create repo and bind origin/gh run tests-> launch test job/gh test status <jobId>-> read test status/output tail
Telegram adaptation notes:
- Plain text messages behave like a normal Codex conversation turn
/execruns a one-off Codex task and does not overwrite the saved project conversation slot/autoruns a one-off Codex task withapprovalPolicy=neveron the SDK backend, orcodex exec --full-autoon the CLI backend/newis implemented by the bot and resets the current chat session/newonly clears the current project's saved Codex conversation slot/statusis implemented by the bot and reports local runtime state/repois implemented by the bot and switches the per-chat working directory insideWORKSPACE_ROOT/skillis implemented by the bot and keeps per-chat skill switches in runtime state/shis implemented by the bot, never invokes a shell interpreter, and only accepts configured command prefixes/shis read-only by default; dangerous prefixes can be configured and require--confirmwhen writable mode is enabled/plantranslates to a planning-only prompt instead of passing a raw/planslash command to Codex- If another chat already has an active Codex run in the same workdir, the bot blocks the new request and requires
/continuefor a one-shot override - The default system language is English; use
/language zhor/language zh-HKfor localized bot responses /verbose offkeeps Telegram output quiet by hiding fallback, startup, and session-exit notices for the current chat
Codex output is streamed with throttled editMessageText updates.
- Throttle: controlled by
STREAM_THROTTLE_MS(default1200) - Long output: auto-chunked to Telegram-safe message sizes
- MarkdownV2: escaped to avoid parse failures
- Reasoning tags:
<think>...</think>extracted and rendered as:- spoiler (
||...||, default) - quote block (if
REASONING_RENDER_MODE=quote)
- spoiler (
- On
CODEX_BACKEND=sdk, Telegram streams structured Codex SDK events and persists thread IDs per project - On
CODEX_BACKEND=cli, the bot prefers PTY sessions; ifnode-ptycannot spawn on the current host, it falls back tocodex exec - In CLI exec fallback mode, Telegram output is cleaned to hide the Codex banner, raw tool trace,
mcp startup, and duplicatetokens usedfooter - On macOS, startup auto-repairs
node-ptyhelper execute permissions before the first PTY session
Conversation state is now tracked per chat + project, not just per chat.
- When you switch with
/repo <name>, the bot keeps that project's last Codex session id in runtime state - When you switch back to the same project later, the next plain-text task resumes that project's Codex thread/session
/newclears only the current project's saved conversation slot; other projects in the same Telegram chat are untouched/exec,/auto, and/planstay one-off by design and do not replace the saved project conversation- On the SDK backend, project restore uses
resumeThread(threadId) - On the CLI backend, project restore uses PTY resume or
codex exec resume
The bot now blocks a second Codex run when another bot-managed chat already has an active Codex task in the same workdir.
- the warning is strong by default because simultaneous writes in the same workdir are easy to corrupt
/continuereplays the most recently blocked request once for the current chat- switching projects clears the pending blocked request
- this guard only sees bot-managed chats in this process; if you also use Codex directly in a terminal, use a separate git worktree to avoid conflicts
Choose the execution backend with CODEX_BACKEND:
sdk- preferred for new installs; avoids PTY fragility and uses persistent Codex SDK threadscli- legacy backend; uses PTY when available and falls back tocodex exec
SDK-related options:
CODEX_BACKEND=sdk
CODEX_SDK_CONFIG={}
CODEX_SDK_SKIP_GIT_REPO_CHECK=true
CODEX_SDK_SANDBOX_MODE=workspace-write
CODEX_SDK_APPROVAL_POLICY=never
CODEX_SDK_REASONING_EFFORT=high
CODEX_SDK_NETWORK_ACCESS_ENABLED=true
CODEX_SDK_WEB_SEARCH_MODE=live
CODEX_SDK_ADDITIONAL_DIRECTORIES=["/abs/path/extra-worktree"]CLI-related options:
CODEX_BACKEND=cli
CODEX_COMMAND=codex
CODEX_ARGS=node-cron is built in for proactive behavior:
- Daily summary schedule:
CRON_DAILY_SUMMARY(default0 9 * * *) - Target users:
PROACTIVE_USER_IDS - Summary includes commit count, changed files, insertions/deletions, and recent commits
Use /cron_now for manual trigger during debugging.
Required:
BOT_TOKEN=...
ALLOWED_USER_IDS=123456789,987654321
STATE_FILE=.codex-telegram-claws-state.json
WORKSPACE_ROOT=.
CODEX_WORKDIR=.Common options:
CODEX_COMMAND=codex
CODEX_ARGS=
CODEX_BACKEND=sdk
CODEX_SDK_CONFIG={}
CODEX_SDK_SKIP_GIT_REPO_CHECK=true
CODEX_SDK_SANDBOX_MODE=
CODEX_SDK_APPROVAL_POLICY=
CODEX_SDK_REASONING_EFFORT=
CODEX_SDK_NETWORK_ACCESS_ENABLED=
CODEX_SDK_WEB_SEARCH_MODE=
CODEX_SDK_ADDITIONAL_DIRECTORIES=[]
WORKSPACE_ROOT=/Users/yourname/projects
STATE_FILE=/path/to/codex-telegram-claws-state.json
SHELL_ENABLED=false
SHELL_READ_ONLY=true
SHELL_ALLOWED_COMMANDS=["pwd","ls","git status","git diff --stat","npm test","npm run check"]
SHELL_DANGEROUS_COMMANDS=["git add","git commit","git push","rm","mv","cp","npm publish"]
SHELL_TIMEOUT_MS=20000
SHELL_MAX_OUTPUT_CHARS=12000
STREAM_THROTTLE_MS=1200
STREAM_BUFFER_CHARS=120000
REASONING_RENDER_MODE=spoiler
CRON_DAILY_SUMMARY=0 9 * * *
CRON_TIMEZONE=Asia/Shanghai
PROACTIVE_USER_IDS=123456789MCP:
MCP_SERVERS=[]GitHub:
GITHUB_TOKEN=ghp_xxx
GITHUB_DEFAULT_WORKDIR=.
GITHUB_DEFAULT_BRANCH=main
E2E_TEST_COMMAND=npx playwright test --reporter=lineGitHub Actions now includes:
CIworkflow on push and pull requestTelegram Smokemanual workflow for live bot-token validation when repository secrets are configuredReleaseworkflow onv*tags, which reruns validation and publishes a GitHub Release
Repository secrets for live smoke checks:
TELEGRAM_BOT_TOKENTELEGRAM_EXPECTED_USERNAME(optional)TELEGRAM_SMOKE_CHAT_ID(optional)
Recommended local release gate:
npm run release:check
npm run healthcheck:liveRelease references:
- operations.md
- release.md
- ecosystem.config.cjs - PM2 compatibility shim
- Whitelist-only access (
ALLOWED_USER_IDS) is mandatory - Do not commit
.env, tokens, or session artifacts - Run bot under a restricted OS user in production
- Keep
CODEX_WORKDIRscoped to a safe workspace root - Keep
WORKSPACE_ROOTlimited to a parent directory that only contains projects you want the bot to access - Keep
/shdisabled unless you need it; when enabled, only expose read-only or narrowly scoped command prefixes /shusesspawn(..., { shell: false }), rejects pipes/redirection/subshell syntax, and runs inside the current project directory- Keep
SHELL_READ_ONLY=trueunless you have a strong reason to allow write commands - If you allow write commands, mark high-risk prefixes in
SHELL_DANGEROUS_COMMANDSand require/sh --confirm ... - Prefer least-privilege GitHub PAT
The recommended production supervisor is PM2.
ecosystem.config.ts is the canonical config file. Start PM2 with ecosystem.config.cjs, which only bridges PM2 into the TypeScript source.
Basic flow:
pm2 start ecosystem.config.cjs
pm2 status codex-telegram-claws
pm2 logs codex-telegram-claws
pm2 restart codex-telegram-clawsRun exactly one polling process per bot token.
Usually not for general users. Codex itself can run commands as part of a coding task, so /sh is not required for normal code-edit workflows.
It is useful when you need deterministic operator actions from Telegram, such as:
pwdgit statusgit diff --statnpm test
Treat it as an admin-only ops channel, not a general-purpose remote shell.
Telegram can manage runtime usage of Bot-side MCP and skills, but not install arbitrary new servers from chat.
- MCP servers are process-level runtime resources: list, inspect, reconnect, enable, disable
- Skills are chat-level routing switches: each chat can enable or disable
githubandmcpindependently - Codex's own MCP remains separate and is not managed through these bot commands
- Runtime state is persisted to
STATE_FILE, so/mcp enable|disable,/skill on|off,/language,/verbose, and per-project Codex conversation slots survive bot restarts
- Bot not responding: verify
BOT_TOKENandALLOWED_USER_IDS - Codex not producing output: verify
CODEX_BACKEND,CODEX_COMMAND, andCODEX_WORKDIR - SDK backend cannot resume: verify the host still has access to
~/.codex/sessionsand that the saved thread id belongs to the same working directory - Markdown parse errors: reduce output size/context; check special characters in tool output
- MCP failures: run
/mcp tools <server>first to validate server availability - GitHub API failures: verify
GITHUB_TOKENscope (repo) and account permissions - Duplicate MCP suspicion: ensure coding tasks are routed directly to Codex, and bot MCP is used only for
/mcp posix_spawnp failed: this usually means thenode-ptyhelper lost execute permissions; startup now auto-repairs it, andnpm run healthcheckreports the result
- Inspired by: https://github.com/RichardAtCT/claude-code-telegram
- Codex SDK reference: https://github.com/coleam00/codex-telegram-coding-assistant
- This implementation: Codex-first Node.js stack (
@openai/codex-sdk,telegraf,node-pty,node-cron, MCP SDK)