13 KiB
Telegram Gateway Operator Guide
This document is the operator and maintainer guide for the Rust Telegram integration in claw-code-parity.
It is written for:
- humans deploying the bot on Unraid or Docker
- future AI agents modifying the deployment
- reviewers trying to understand how the gateway, workers, and manifest fit together
What Exists
There are three distinct runtime modes:
-
claw-telegram gateway serve- production gateway mode
- owns the Telegram bot token
- polls Telegram
- reads the manifest
- creates and reconciles worker containers through the Docker socket
-
claw-telegram standalone serve- legacy single-process mode
- one process handles Telegram and the runtime directly
- does not use the gateway/worker split
-
claw-profile-worker serve- internal worker process
- one worker per profile
- normally started by the gateway, not by end users
Architecture
The gateway/worker model is the intended deployment.
Topology:
- One Telegram bot token
- One gateway process
- One manifest file
- One worker container per profile
Important invariants:
- one Telegram bot token must have one inbound owner
- the gateway is the only process that should poll Telegram
- workers do not talk to Telegram directly
- workers are selected by profile mapping from the manifest
- one profile owns one isolated worker container
- one profile may have multiple channel identities in the future
- today, only Telegram DM identities are implemented
Core Files And State
Gateway manifest:
- default in-container path:
/appdata/profiles.json - default Unraid host path:
/mnt/user/appdata/claw-telegram-gateway/profiles.json
Gateway runtime state:
- default in-container path:
/appdata/state - used for Telegram update offsets and downloaded/generated files staged by the gateway
Generated Unraid worker templates:
- live Unraid directory:
/boot/config/plugins/dockerMan/templates-user/
Important Unraid fact:
- the current value of each config field is stored in the text body of each
<Config>...</Config>element - replacing the live XML with a blank repo copy can wipe stored values
Which Template To Use
For the gateway/worker deployment, use the gateway template:
Do not confuse it with the legacy standalone template:
- standalone mode expects
CLAW_TELEGRAM_BOT_TOKEN - gateway mode expects
CLAW_GATEWAY_TELEGRAM_BOT_TOKEN
If the container logs mention CLAW_TELEGRAM_BOT_TOKEN, the container is almost certainly starting in standalone mode instead of gateway mode.
Required Gateway Settings
The gateway container needs:
-
Telegram bot token
CLAW_GATEWAY_TELEGRAM_BOT_TOKEN
-
worker API bearer token
CLAW_WORKER_AUTH_TOKEN
-
worker image
CLAW_GATEWAY_WORKER_IMAGE
-
appdata mount
- host path usually
/mnt/user/appdata/claw-telegram-gateway - mounted to
/appdata
- host path usually
-
Docker socket mount
/var/run/docker.sock:/var/run/docker.sock
-
Unraid templates mount
/boot/config/plugins/dockerMan/templates-user:/unraid/templates-user
-
Docker network
claw_gateway
-
ANTHROPIC_AUTH_TOKEN- Claude subscription OAuth token such as
sk-ant-oat... - the gateway now sends the extra Claude Code headers required for this path
- Claude subscription OAuth token such as
-
ANTHROPIC_API_KEY- standard Anthropic API key
If you use ANTHROPIC_AUTH_TOKEN, the gateway workers add the Claude Code OAuth headers and agent identity prefix that nanobot also uses for subscription auth.
The gateway copies inherited auth env vars into worker containers according to CLAW_GATEWAY_INHERITED_ENV.
First Boot
Expected first-boot behavior:
- The gateway starts with
claw-telegram gateway serve - If the manifest does not exist yet, the gateway creates an empty manifest automatically
- The gateway connects to Telegram
- Users are rejected until they are mapped in the manifest
The gateway does not auto-map Telegram users from chat messages.
Manifest Model
The manifest is the source of truth for routing.
Each profile contains:
profile_id- optional
display_name worker- container name
- host state directory
- host workspace directory
channels- today: Telegram DM user IDs
Example:
{
"version": 1,
"gateway": {
"worker_image": "git.wylab.me/wylab/claw-telegram:latest",
"worker_network": "claw_gateway",
"template_dir": "/unraid/templates-user",
"template_file_prefix": "claw-worker-",
"template_archive_dir": "/appdata/template-archive",
"inherited_env": ["ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_API_KEY"]
},
"worker_defaults": {
"bind_port": 8080,
"default_cwd": "/workspace",
"permission_mode": "workspace-write",
"model": "claude-opus-4-6",
"host_state_root": "/mnt/user/appdata/claw-workers",
"host_workspace_root": "/mnt/user/appdata/claw-workers"
},
"profiles": [
{
"profile_id": "makar",
"display_name": "Makar",
"worker": {
"container_name": "claw-worker-makar",
"host_state_dir": "/mnt/user/appdata/claw-workers/makar/state",
"host_workspace_dir": "/mnt/user/appdata/claw-workers/makar/workspace"
},
"channels": [
{
"channel": "telegram",
"kind": "dm",
"user_id": 239824268
}
]
}
]
}
Validation rules:
- profile IDs must be unique
- worker container names must be unique
- host state directories must be unique
- host workspace directories must be unique
- Telegram user IDs must be unique across all profiles
Command Reference
The command surface is implemented in rust/crates/claw-telegram/src/main.rs and rust/crates/claw-profile-worker/src/main.rs.
claw-telegram
Top-level commands:
-
claw-telegram serve- alias for legacy standalone mode
-
claw-telegram standalone serve- starts the old monolithic Telegram bot
-
claw-telegram gateway serve- starts the gateway mode
-
claw-telegram workers reconcile- loads the manifest
- creates missing worker templates
- creates missing worker containers
- restarts stopped workers
- archives removed templates
- removes deleted managed containers
-
claw-telegram profiles add <profile-id> [--user-id <telegram-user-id>] [--display-name <name>]- creates a new profile
- derives worker container and host paths
- optionally attaches a Telegram user
- saves manifest
- reconciles workers
-
claw-telegram profiles remove <profile-id>- removes a profile from the manifest
- reconciles workers
- removes the container
- keeps host state/workspace paths
-
claw-telegram profiles merge <source-profile-id> <target-profile-id>- moves the source profile channel identities into the target profile
- removes the source profile
- reconciles workers
- keeps source state/workspace paths
-
claw-telegram profiles channel add <profile-id> telegram <user-id>- attaches a Telegram DM identity to an existing profile
- saves manifest
- reconciles workers
-
claw-telegram profiles channel remove telegram <user-id>- removes a Telegram DM identity from whatever profile currently owns it
- saves manifest
- reconciles workers
-
claw-telegram migrate-standalone-registry [registry-path]- imports old standalone registry data into the gateway manifest
- then reconciles workers
claw-profile-worker
claw-profile-worker serve- starts one worker instance
- expects worker env vars to already be present
Practical Operator Workflows
Start the gateway
If the container entrypoint is configured correctly, Unraid should start:
claw-telegram gateway serve
Add the first Telegram user
Example:
docker exec -it claw-telegram-gateway \
claw-telegram profiles add makar --user-id 239824268 --display-name "Makar"
What this does:
- creates profile
makar - maps Telegram user
239824268 - writes the manifest
- reconciles worker state
- creates the worker template and worker container
Add another mapped user to an existing profile
docker exec -it claw-telegram-gateway \
claw-telegram profiles channel add makar telegram 123456789
Remove a profile
docker exec -it claw-telegram-gateway \
claw-telegram profiles remove makar
Merge two mistaken profiles
docker exec -it claw-telegram-gateway \
claw-telegram profiles merge old-profile makar
Force a resync after manual manifest edits
docker exec -it claw-telegram-gateway \
claw-telegram workers reconcile
Telegram Chat Commands
Once a Telegram user is mapped to a profile, the bot supports:
/start/help/status/new/cancel
These are user-facing chat commands, not gateway administration commands.
How Routing Works
When a Telegram DM arrives:
- the gateway polls Telegram
- the gateway reads
from.id - the gateway loads the manifest
- the gateway finds the profile whose
channelsinclude that Telegram user ID - the gateway ensures that profile's worker exists
- the gateway sends the turn to that worker
- the worker streams events back
- the gateway renders those events back to Telegram
If no profile matches that Telegram user ID, the bot replies:
You are not mapped to a profile in the gateway manifest.
That message means the gateway is healthy enough to receive updates, but the sender is not registered yet.
Worker Lifecycle
Workers are managed by the gateway through the Docker socket.
The gateway:
- creates host state/workspace directories
- writes a worker template XML for each profile
- creates the worker container from
CLAW_GATEWAY_WORKER_IMAGE - starts it
- health-checks it over HTTP
Current worker startup command:
- entrypoint:
claw-profile-worker - args:
serve
Unraid-Specific Notes
Network mode
The gateway container must not run with --net=none.
Use:
claw_gatewayfor the real deployment
Why:
- the gateway needs outbound HTTPS to Telegram
- the gateway also needs network reachability to worker containers
Docker socket access
The gateway image currently runs as root by default.
Reason:
- the gateway must open
/var/run/docker.sock - the current worker bind-mount model also depends on writable host paths without an explicit
PUID/PGIDownership flow
Live template values
Unraid stores actual values in the live template file under:
/boot/config/plugins/dockerMan/templates-user/
Do not blindly overwrite a configured live template with a blank repo copy after you have entered secrets.
Gateway template vs standalone template
If you are deploying the gateway/worker architecture:
- use the gateway template
- do not use the legacy standalone template
If you see logs complaining about CLAW_TELEGRAM_BOT_TOKEN, you are almost certainly starting the standalone path, not the gateway path.
Troubleshooting
You are not mapped to a profile in the gateway manifest.
Cause:
- the sender's Telegram user ID is not attached to any profile
Fix:
- add the user ID with
profiles addorprofiles channel add
missing required environment variable CLAW_TELEGRAM_BOT_TOKEN
Cause:
- the container started in standalone mode
- or the old standalone template is being used
Fix:
- ensure the gateway container starts with
gateway serve - ensure the template uses
CLAW_GATEWAY_TELEGRAM_BOT_TOKEN
gateway manifest does not exist at /appdata/profiles.json
Cause:
- older gateway build
Current behavior:
- first boot now auto-creates the manifest if it does not exist
Permission denied (os error 13) when opening Docker socket
Cause:
- gateway container was running as an unprivileged user
Current behavior:
- published gateway image now runs as root by default so it can access
/var/run/docker.sock
error sending request for url (.../getMe)
Meaning:
- the Telegram token is loaded
- the HTTPS request itself failed before Telegram returned an API payload
Likely causes:
- container network mode is
none - DNS failure
- outbound 443 blocked
- TLS or host time issue
Template values disappear after saving in Unraid
Likely causes:
- using a stale live template
- overwriting the live template file with a blank repo copy
- using a malformed template where variable fields are self-closing instead of carrying body text
Operational rule:
- the repo template is the seed
- the live template in
templates-useris where Unraid persists actual values
Notes For Future Maintainers And AI Agents
Do not change these assumptions casually:
- one Telegram bot token must have one gateway owner
- the manifest is the routing source of truth
- workers are per-profile and isolated by host directories
- the gateway template and worker templates are part of the operational surface, not just examples
- Unraid stores live config values in the live XML files, not only in the UI
- the published image must contain both
claw-telegramandclaw-profile-worker - the gateway image must be able to talk to the Docker socket
If you modify commands, env vars, or manifest behavior:
- update this guide
- update the gateway template
- update the worker template generator
- keep the CLI help output and docs aligned