This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a Railway deployment wrapper for OpenClaw (an AI coding assistant platform). It provides:
- A web-based setup wizard at
/setup(protected bySETUP_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.
# Local development (requires OpenClaw installed globally or OPENCLAW_ENTRY set)
npm run dev
# Production start
npm start
# Syntax check
npm run lint# 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-
User → Railway → Wrapper (Express on PORT) → routes to:
/setup/*→ setup wizard (auth: Basic withSETUP_PASSWORD)- All other routes → proxied to internal gateway
-
Wrapper → Gateway (localhost:18789 by default)
- HTTP/WebSocket reverse proxy via
http-proxy - Automatically injects
Authorization: Bearer <token>header
- HTTP/WebSocket reverse proxy via
-
Unconfigured: No
openclaw.jsonexists- All non-
/setuproutes redirect to/setup - User completes setup wizard → runs
openclaw onboard --non-interactive
- All non-
-
Configured:
openclaw.jsonexists- Wrapper spawns
openclaw gateway runas child process - Waits for gateway to respond on multiple health endpoints
- Proxies all traffic with injected bearer token
- Wrapper spawns
- 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
/setupwizard (vanilla JS, no build step)
- Dockerfile: Single-stage build (installs OpenClaw via npm, installs wrapper deps)
Required:
SETUP_PASSWORD— protects/setupwizard
Recommended (Railway template defaults):
OPENCLAW_STATE_DIR=/data/.openclaw— config + credentialsOPENCLAW_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 toentry.js(default/usr/local/lib/node_modules/openclaw/dist/entry.js)
The wrapper manages a two-layer auth scheme:
- Setup wizard auth: Basic auth with
SETUP_PASSWORD(src/server.js:190) - Gateway auth: Bearer token (auto-generated or from
OPENCLAW_GATEWAY_TOKENenv)- Token is auto-injected into proxied requests (src/server.js:736, src/server.js:741)
- Persisted to
${STATE_DIR}/gateway.tokenif not provided via env (src/server.js:25-48)
When the user runs setup (src/server.js:522-693):
- Calls
openclaw onboard --non-interactivewith user-selected auth provider - Writes channel configs (Telegram/Discord/Slack) directly to
openclaw.jsonviaopenclaw config set --json - Force-sets gateway config to use token auth + loopback bind + allowInsecureAuth
- Spawns gateway process
- 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.
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.
- Delete
${STATE_DIR}/openclaw.json(or run Reset in the UI) - Visit
/setupand complete onboarding - Check logs for gateway startup and channel config writes
- Setup wizard: Clear browser auth, verify Basic auth challenge
- Gateway: Remove
Authorizationheader injection (src/server.js:736) and verify requests fail
Check logs for:
[gateway] starting with command: ...(src/server.js:142)[gateway] ready at <endpoint>(src/server.js:100)[gateway] failed to become ready after 20000ms(src/server.js:109)
If gateway doesn't start:
- Verify
openclaw.jsonexists and is valid JSON - Check
STATE_DIRandWORKSPACE_DIRare writable - Ensure bearer token is set in config
Edit buildOnboardArgs() (src/server.js:442-496) to add new CLI flags or auth providers.
- Add channel-specific fields to
/setupHTML (src/public/setup.html) - Add config-writing logic in
/setup/api/runhandler (src/server.js) - Update client JS to collect the fields (src/public/setup-app.js)
- Template must mount a volume at
/data - Must set
SETUP_PASSWORDin Railway Variables - Public networking must be enabled (assigns
*.up.railway.appdomain) - OpenClaw is installed via
npm install -g openclaw@latestduring Docker build
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:
- Check
mcp__serena__check_onboarding_performedfirst to see available memories - Read relevant memory files before diving into code (e.g.,
mcp__serena__read_memory) - Use Serena's semantic tools for efficient code exploration:
get_symbols_overview- Get high-level file structure without reading entire filefind_symbol- Find classes, functions, methods by name pathfind_referencing_symbols- Understand dependencies and usage
- 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.
- Gateway token must be stable across redeploys → persisted to volume if not in env
- Channels are written via
config set --json, notchannels add→ avoids CLI version incompatibilities - Gateway readiness check polls multiple endpoints (
/openclaw,/,/health) → some builds only expose certain routes (src/server.js:92) - Discord bots require MESSAGE CONTENT INTENT → document this in setup wizard (src/server.js:295-298)
- Gateway spawn inherits stdio → logs appear in wrapper output (src/server.js:134)
- WebSocket auth requires proxy event handlers → Direct
req.headersmodification doesn't work for WebSocket upgrades with http-proxy; must useproxyReqWsevent (src/server.js:741) to reliably inject Authorization header - Control UI requires allowInsecureAuth to bypass pairing → Set
gateway.controlUi.allowInsecureAuth=trueduring onboarding to prevent "disconnected (1008): pairing required" errors (GitHub issue #2284). Wrapper already handles bearer token auth, so device pairing is unnecessary.