Run the codex CLI from Telegram with per-chat sessions, machine selection (local/SSH), and an approvals UI for command execution.
This is designed for private, single-operator use: you allowlist your Telegram user ID(s), then you can run Codex from your phone or desktop Telegram client.
- Private by default: Telegram user allowlist.
- Stateful: persists a per-chat Codex session ID (resume across restarts).
- Multi-machine: run on local or SSH targets.
- Safer execution UX: inline approvals for exec requests (accept once / accept similar / reject).
- Telegram-friendly output: buffered streaming with typing indicator.
- Python >= 3.10
- A Telegram bot token (create via @BotFather)
- The
codexCLI installed and available on each machine you run on - Optional:
asyncsshfor SSH machines (installed by default on non-Windows)
From the repo (recommended):
python -m pip install -e .Or directly from GitHub:
python -m pip install "git+https://github.com/poseidonchan/codex-telegram-bot.git"- Create a config using the setup wizard:
tgcodex-bot setup --config config.yaml- Export your bot token into the env var you chose in the wizard (default:
TELEGRAM_BOT_TOKEN):
export TELEGRAM_BOT_TOKEN="123456:ABC...keep_this_secret"- Validate config:
tgcodex-bot validate-config --config config.yaml- Start the bot:
# Detached background process (recommended for servers)
tgcodex-bot start --config config.yaml
# Foreground (useful for debugging)
tgcodex-bot run --config config.yaml- In Telegram, open a private chat with your bot and send:
/start
/menu
For a full reference, see config.example.yaml.
Minimal example:
telegram:
token_env: TELEGRAM_BOT_TOKEN
allowed_user_ids: [123456789]
state:
db_path: ~/.tgcodex/tgcodex.sqlite3
codex:
bin: codex
args: []
model: null
sandbox: workspace-write
approval_policy: on-request
skip_git_repo_check: true
machines:
default: local
defs:
local:
type: local
default_workdir: /home/ubuntu
allowed_roots: [/home/ubuntu, /tmp]Notes:
telegram.allowed_user_idsis your security boundary: if the user ID is not listed, the bot refuses requests.allowed_rootsrestricts/cdand path resolution to an allowlist.codex.approval_policysets the default approval behavior for new chats (recommended:on-request). You can change it per chat with/approval.- For SSH machines you can override the remote
codexpath withmachines.defs.<name>.codex_bin.
tgcodex-bot start --config config.yaml
tgcodex-bot status --config config.yaml
tgcodex-bot stop --config config.yamlBy default the runtime artifacts are created next to your config:
- PID file:
./.tgcodex-bot/config.yaml.pid - Log file:
./.tgcodex-bot/config.yaml.log
Log tail:
tail -f .tgcodex-bot/config.yaml.logtgcodex-bot run --config config.yamlStart here:
/start: health check/menu: list commands/status: current machine/workdir/session + token telemetry
Session management:
/new: clear the active session (next message starts fresh)/rename <title>: set the current session title/resume: pick a recent session to resume/exit: cancel an active run and clear session state
Environment:
/machine <name>: switch machine (clears session)/cd <path>: change working directory (restricted byallowed_roots)
Run behavior:
/approval [on-request|yolo]: update approval mode (legacy aliases likeuntrusted,always,neverstill work)/sandbox [read-only|workspace-write|danger-full-access]: set sandbox mode for this chat (applies next message; clears session)/plan: toggle “plan mode”/compact: compact the active session and continue in a new one/model [slug] [effort]: pick a model (and thinking level if supported)/skills: list available Codex skills (on the active machine)/mcp: list MCP servers configured for Codex
Tip:
- If you want to send a literal slash-prefixed prompt to Codex, you can type
//...in Telegram and it will be rewritten to/....
Execution approvals are shown as inline buttons:
- Accept once
- Accept similar (stores a trusted prefix for this session)
- Reject
Approval modes:
on-request: approvals are required for commands outside Codex's trusted set; the bot shows inline Approve/Reject buttons and waits.yolo: auto-accept approvals (no prompts). Sandbox stays enabled.
These are separate controls:
-
Sandbox controls what Codex is allowed to do when it runs tools/commands.
read-only: Codex can inspect, but should not modify files.workspace-write: Codex can write within the allowed workspace scope.danger-full-access: broad permissions; use only when you understand the risk.
-
YOLO controls approvals only.
- In tgcodex,
yolomeans the bot auto-accepts approval requests. - YOLO does not disable the sandbox. To change sandbox behavior, use
/sandbox.
- In tgcodex,
You can define multiple machines and switch per chat with /machine.
Local machine:
- Runs on the host where
tgcodex-botis running.
SSH machine:
- Runs
codexremotely viaasyncssh. - Requires a reachable SSH host and correct auth settings.
- If remote PATH isn’t initialized for non-interactive shells, set
machines.defs.<name>.codex_binto an absolutecodexpath.
- Confirm it’s running:
tgcodex-bot status --config config.yaml- Check logs:
tail -n 200 .tgcodex-bot/config.yaml.log- Confirm you’re allowlisted:
- Your Telegram user ID must be in
telegram.allowed_user_ids.
- Ensure only one poller:
- Running two instances with the same token will cause
getUpdatesconflicts.
- If your chat is set to an unreachable SSH machine:
- Switch back to local:
/machine local
- Ensure
codexis installed and inPATHon the active machine. - For SSH machines, set
machines.defs.<name>.codex_binto the absolute path.
Install dev dependencies:
python -m pip install -e ".[dev]"Run tests:
python -m pytest -qLint (if you use ruff):
ruff check .- Keep your bot token secret (use env vars; never commit it).
- Do not commit
config.yaml(this repo ignores it by default). - Logs and runtime files live in
./.tgcodex-bot/(also ignored by default). - You are running a tool that can execute commands; use approval policies appropriately.
MIT. See LICENSE.