# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Overview This is a Railway deployment wrapper for **OpenClaw** (an AI coding assistant platform). It provides: - A web-based setup wizard at `/setup` (protected by `SETUP_PASSWORD`) - Automatic reverse proxy from public URL → internal OpenClaw gateway - Persistent state via Railway Volume at `/data` The wrapper manages the OpenClaw lifecycle: onboarding → gateway startup → traffic proxying. ## Development Commands ```bash # Local development (requires OpenClaw installed globally or OPENCLAW_ENTRY set) npm run dev # Production start npm start # Syntax check npm run lint ``` ## Docker Build & Local Testing ```bash # Build the container docker build -t openclaw-railway-template . # Run locally with volume docker run --rm -p 8080:8080 \ -e PORT=8080 \ -e SETUP_PASSWORD=test \ -e OPENCLAW_STATE_DIR=/data/.openclaw \ -e OPENCLAW_WORKSPACE_DIR=/data/workspace \ -v $(pwd)/.tmpdata:/data \ openclaw-railway-template # Access setup wizard open http://localhost:8080/setup # password: test ``` ## Architecture ### Request Flow 1. **User → Railway → Wrapper (Express on PORT)** → routes to: - `/setup/*` → setup wizard (auth: Basic with `SETUP_PASSWORD`) - All other routes → proxied to internal gateway 2. **Wrapper → Gateway** (localhost:18789 by default) - HTTP/WebSocket reverse proxy via `http-proxy` - Automatically injects `Authorization: Bearer ` header ### Lifecycle States 1. **Unconfigured**: No `openclaw.json` exists - All non-`/setup` routes redirect to `/setup` - User completes setup wizard → runs `openclaw onboard --non-interactive` 2. **Configured**: `openclaw.json` exists - Wrapper spawns `openclaw gateway run` as child process - Waits for gateway to respond on multiple health endpoints - Proxies all traffic with injected bearer token ### Key Files - **src/server.js** (main entry): Express wrapper, proxy setup, gateway lifecycle management, configuration persistence (server logic only - no inline HTML/CSS) - **src/public/** (static assets for setup wizard): - **setup.html**: Setup wizard HTML structure - **styles.css**: Setup wizard styling (extracted from inline styles) - **setup-app.js**: Client-side JS for `/setup` wizard (vanilla JS, no build step) - **Dockerfile**: Single-stage build (installs OpenClaw via npm, installs wrapper deps) ### Environment Variables **Required:** - `SETUP_PASSWORD` — protects `/setup` wizard **Recommended (Railway template defaults):** - `OPENCLAW_STATE_DIR=/data/.openclaw` — config + credentials - `OPENCLAW_WORKSPACE_DIR=/data/workspace` — agent workspace **Optional:** - `OPENCLAW_GATEWAY_TOKEN` — auth token for gateway (auto-generated if unset) - `PORT` — wrapper HTTP port (default 8080) - `INTERNAL_GATEWAY_PORT` — gateway internal port (default 18789) - `OPENCLAW_ENTRY` — path to `entry.js` (default `/usr/local/lib/node_modules/openclaw/dist/entry.js`) ### Authentication Flow The wrapper manages a **two-layer auth scheme**: 1. **Setup wizard auth**: Basic auth with `SETUP_PASSWORD` (src/server.js:190) 2. **Gateway auth**: Bearer token (auto-generated or from `OPENCLAW_GATEWAY_TOKEN` env) - Token is auto-injected into proxied requests (src/server.js:736, src/server.js:741) - Persisted to `${STATE_DIR}/gateway.token` if not provided via env (src/server.js:25-48) ### Onboarding Process When the user runs setup (src/server.js:522-693): 1. Calls `openclaw onboard --non-interactive` with user-selected auth provider 2. Writes channel configs (Telegram/Discord/Slack) directly to `openclaw.json` via `openclaw config set --json` 3. Force-sets gateway config to use token auth + loopback bind + allowInsecureAuth 4. Spawns gateway process 5. Waits for gateway readiness (polls multiple endpoints) **Important**: Channel setup bypasses `openclaw channels add` and writes config directly because `channels add` is flaky across different OpenClaw builds. ### Gateway Token Injection The wrapper **always** injects the bearer token into proxied requests so browser clients don't need to know it: - HTTP requests: via `proxy.on("proxyReq")` event handler (src/server.js:736) - WebSocket upgrades: via `proxy.on("proxyReqWs")` event handler (src/server.js:741) **Important**: Token injection uses `http-proxy` event handlers (`proxyReq` and `proxyReqWs`) rather than direct `req.headers` modification. Direct header modification does not reliably work with WebSocket upgrades, causing intermittent `token_missing` or `token_mismatch` errors. This allows the Control UI at `/openclaw` to work without user authentication. ## Common Development Tasks ### Testing the setup wizard 1. Delete `${STATE_DIR}/openclaw.json` (or run Reset in the UI) 2. Visit `/setup` and complete onboarding 3. Check logs for gateway startup and channel config writes ### Testing authentication - Setup wizard: Clear browser auth, verify Basic auth challenge - Gateway: Remove `Authorization` header injection (src/server.js:736) and verify requests fail ### Debugging gateway startup Check logs for: - `[gateway] starting with command: ...` (src/server.js:142) - `[gateway] ready at ` (src/server.js:100) - `[gateway] failed to become ready after 20000ms` (src/server.js:109) If gateway doesn't start: - Verify `openclaw.json` exists and is valid JSON - Check `STATE_DIR` and `WORKSPACE_DIR` are writable - Ensure bearer token is set in config ### Modifying onboarding args Edit `buildOnboardArgs()` (src/server.js:442-496) to add new CLI flags or auth providers. ### Adding new channel types 1. Add channel-specific fields to `/setup` HTML (src/public/setup.html) 2. Add config-writing logic in `/setup/api/run` handler (src/server.js) 3. Update client JS to collect the fields (src/public/setup-app.js) ## Railway Deployment Notes - Template must mount a volume at `/data` - Must set `SETUP_PASSWORD` in Railway Variables - Public networking must be enabled (assigns `*.up.railway.app` domain) - OpenClaw is installed via `npm install -g openclaw@latest` during Docker build ## Serena Semantic Coding This project has been onboarded with **Serena** (semantic coding assistant via MCP). Comprehensive memory files are available covering: - Project overview and architecture - Tech stack and codebase structure - Code style and conventions - Development commands and task completion checklist - Quirks and gotchas **When working on tasks:** 1. Check `mcp__serena__check_onboarding_performed` first to see available memories 2. Read relevant memory files before diving into code (e.g., `mcp__serena__read_memory`) 3. Use Serena's semantic tools for efficient code exploration: - `get_symbols_overview` - Get high-level file structure without reading entire file - `find_symbol` - Find classes, functions, methods by name path - `find_referencing_symbols` - Understand dependencies and usage 4. Prefer symbolic editing (`replace_symbol_body`, `insert_after_symbol`) for precise modifications This avoids repeatedly reading large files and provides instant context about the project. ## Quirks & Gotchas 1. **Gateway token must be stable across redeploys** → persisted to volume if not in env 2. **Channels are written via `config set --json`, not `channels add`** → avoids CLI version incompatibilities 3. **Gateway readiness check polls multiple endpoints** (`/openclaw`, `/`, `/health`) → some builds only expose certain routes (src/server.js:92) 4. **Discord bots require MESSAGE CONTENT INTENT** → document this in setup wizard (src/server.js:295-298) 5. **Gateway spawn inherits stdio** → logs appear in wrapper output (src/server.js:134) 6. **WebSocket auth requires proxy event handlers** → Direct `req.headers` modification doesn't work for WebSocket upgrades with http-proxy; must use `proxyReqWs` event (src/server.js:741) to reliably inject Authorization header 7. **Control UI requires allowInsecureAuth to bypass pairing** → Set `gateway.controlUi.allowInsecureAuth=true` during onboarding to prevent "disconnected (1008): pairing required" errors (GitHub issue #2284). Wrapper already handles bearer token auth, so device pairing is unnecessary. 8. **Config Editor (`/setup/config`) writes `openclaw.json` directly** → unlike onboarding (which uses `openclaw config set --json`), the editor parses+validates JSON, makes a `.bak-` copy, then `fs.writeFileSync`s the new contents and calls `restartGateway()`. This is intentional: it's an operator-targeted advanced tool, so direct writes are simpler and let users fix arbitrarily-broken config without depending on the CLI.