Guidelines for AI agents working on this codebase.
This is a Cloudflare Worker that runs ZeroClaw in a Cloudflare Sandbox container. It provides:
- Proxying to the ZeroClaw gateway (web UI + WebSocket)
- Admin UI at
/_admin/for device management - API endpoints at
/api/*for device pairing - Debug endpoints at
/debug/*for troubleshooting
Note: The CLI tool and npm package are now named zeroclaw. Config files use .zeroclaw/config.toml. Legacy .clawdbot paths are supported for backward compatibility during transition.
src/
├── index.ts # Main Hono app, route mounting
├── types.ts # TypeScript type definitions
├── config.ts # Constants (ports, timeouts, paths)
├── auth/ # Cloudflare Access authentication
│ ├── jwt.ts # JWT verification
│ ├── jwks.ts # JWKS fetching and caching
│ └── middleware.ts # Hono middleware for auth
├── gateway/ # ZeroClaw gateway management
│ ├── process.ts # Process lifecycle (find, start)
│ ├── env.ts # Environment variable building
│ ├── r2.ts # R2 bucket mounting
│ ├── sync.ts # R2 backup sync logic
│ └── utils.ts # Shared utilities (waitForProcess)
├── routes/ # API route handlers
│ ├── api.ts # /api/* endpoints (devices, gateway)
│ ├── admin.ts # /_admin/* static file serving
│ └── debug.ts # /debug/* endpoints
└── client/ # React admin UI (Vite)
├── App.tsx
├── api.ts # API client
└── pages/
DEV_MODE- Skips CF Access auth AND bypasses device pairing (maps toZEROCLAW_DEV_MODEfor container)DEBUG_ROUTES- Enables/debug/*routes (disabled by default)- See
src/types.tsfor fullMoltbotEnvinterface
CLI commands take 10-15 seconds due to WebSocket connection overhead. Use waitForProcess() helper in src/routes/api.ts.
The CLI outputs "Approved" (capital A). Use case-insensitive checks:
stdout.toLowerCase().includes('approved')npm test # Run tests (vitest)
npm run test:watch # Run tests in watch mode
npm run build # Build worker + client
npm run deploy # Build and deploy to Cloudflare
npm run dev # Vite dev server
npm run start # wrangler dev (local worker)
npm run typecheck # TypeScript checkTests use Vitest. Test files are colocated with source files (*.test.ts).
Current test coverage:
auth/jwt.test.ts- JWT decoding and validationauth/jwks.test.ts- JWKS fetching and cachingauth/middleware.test.ts- Auth middleware behaviorgateway/env.test.ts- Environment variable buildinggateway/process.test.ts- Process finding logicgateway/r2.test.ts- R2 mounting logicgateway/sync.test.ts- R2 backup sync logic
When adding new functionality, add corresponding tests.
- Use TypeScript strict mode
- Prefer explicit types over inference for function signatures
- Keep route handlers thin - extract logic to separate modules
- Use Hono's context methods (
c.json(),c.html()) for responses
README.md- User-facing documentation (setup, configuration, usage)AGENTS.md- This file, for AI agents
Development documentation goes in AGENTS.md, not README.md.
Browser
│
▼
┌─────────────────────────────────────┐
│ Cloudflare Worker (index.ts) │
│ - Starts ZeroClaw in sandbox │
│ - Proxies HTTP/WebSocket requests │
│ - Passes secrets as env vars │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Cloudflare Sandbox Container │
│ ┌───────────────────────────────┐ │
│ │ ZeroClaw Gateway │ │
│ │ - Control UI on port 42617 │ │
│ │ - WebSocket RPC protocol │ │
│ │ - Agent runtime │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
| File | Purpose |
|---|---|
src/index.ts |
Worker that manages sandbox lifecycle and proxies requests |
Dockerfile |
Container image based on cloudflare/sandbox with Node 22 + ZeroClaw |
start-zeroclaw.sh |
Startup script: R2 restore → onboard → config patch → launch gateway |
wrangler.jsonc |
Cloudflare Worker + Container configuration |
npm install
cp .dev.vars.example .dev.vars
# Edit .dev.vars with your ANTHROPIC_API_KEY
npm run startFor local development, create .dev.vars:
ANTHROPIC_API_KEY=sk-ant-...
DEV_MODE=true # Skips CF Access auth + device pairing
DEBUG_ROUTES=true # Enables /debug/* routesLocal development with wrangler dev has issues proxying WebSocket connections through the sandbox. HTTP requests work but WebSocket connections may fail. Deploy to Cloudflare for full functionality.
The Dockerfile includes a cache bust comment. When changing start-zeroclaw.sh, bump the version:
# Build cache bust: 2026-02-06-v28-zeroclaw-upgradeZeroClaw configuration is built at container startup:
- R2 backup is restored if available (with migration from legacy
.clawdbotpaths) - If no config exists,
zeroclaw onboard --non-interactivecreates one based on env vars start-zeroclaw.shpatches the config for channels, gateway auth, and trusted proxies- Gateway starts with
zeroclaw gateway --allow-unconfigured --bind lan
The startup script selects the auth choice based on which env vars are set:
- Cloudflare AI Gateway (native):
CLOUDFLARE_AI_GATEWAY_API_KEY+CF_AI_GATEWAY_ACCOUNT_ID+CF_AI_GATEWAY_GATEWAY_ID - Direct Anthropic:
ANTHROPIC_API_KEY(optionally withANTHROPIC_BASE_URL) - Direct OpenAI:
OPENAI_API_KEY - Legacy AI Gateway:
AI_GATEWAY_API_KEY+AI_GATEWAY_BASE_URL(routes through Anthropic base URL)
These are the env vars passed TO the container (internal names):
| Variable | Config Path | Notes |
|---|---|---|
ANTHROPIC_API_KEY |
(env var) | ZeroClaw reads directly from env |
OPENAI_API_KEY |
(env var) | ZeroClaw reads directly from env |
CLOUDFLARE_AI_GATEWAY_API_KEY |
(env var) | Native AI Gateway key |
CF_AI_GATEWAY_ACCOUNT_ID |
(env var) | Account ID for AI Gateway |
CF_AI_GATEWAY_GATEWAY_ID |
(env var) | Gateway ID for AI Gateway |
ZEROCLAW_GATEWAY_TOKEN |
--token flag |
Mapped from MOLTBOT_GATEWAY_TOKEN |
ZEROCLAW_DEV_MODE |
controlUi.allowInsecureAuth |
Mapped from DEV_MODE |
TELEGRAM_BOT_TOKEN |
channels.telegram.botToken |
|
DISCORD_BOT_TOKEN |
channels.discord.token |
|
SLACK_BOT_TOKEN |
channels.slack.botToken |
|
SLACK_APP_TOKEN |
channels.slack.appToken |
ZeroClaw has strict config validation. Common gotchas:
agents.defaults.modelmust be{ "primary": "model/name" }not a stringgateway.modemust be"local"for headless operation- No
webchatchannel - the Control UI is served automatically gateway.bindis not a config option - use--bindCLI flag
See ZeroClaw docs for full schema.
- Add route handler in
src/routes/api.ts - Add types if needed in
src/types.ts - Update client API in
src/client/api.tsif frontend needs it - Add tests
- Add to
MoltbotEnvinterface insrc/types.ts - If passed to container, add to
buildEnvVars()insrc/gateway/env.ts - Update
.dev.vars.example - Document in README.md secrets table
# View live logs
npx wrangler tail
# Check secrets
npx wrangler secret listEnable debug routes with DEBUG_ROUTES=true and check /debug/processes.
R2 is mounted via s3fs at /data/moltbot-zero. Important gotchas:
-
rsync compatibility: Use
rsync -r --no-timesinstead ofrsync -a. s3fs doesn't support setting timestamps, which causes rsync to fail with "Input/output error". -
Mount checking: Don't rely on
sandbox.mountBucket()error messages to detect "already mounted" state. Instead, checkmount | grep s3fsto verify the mount status. -
Never delete R2 data: The mount directory
/data/moltbot-zeroIS the R2 bucket. Runningrm -rf /data/moltbot-zero/*will DELETE your backup data. Always check mount status before any destructive operations. -
Process status: The sandbox API's
proc.statusmay not update immediately after a process completes. Instead of checkingproc.status === 'completed', verify success by checking for expected output (e.g., timestamp file exists after sync). -
R2 prefix migration: Backups are now stored under
zeroclaw/prefix in R2. The startup script handles restoring from both old and new prefixes with automatic migration.