This guide covers local development environment setup and workflows for contributing to the Serverless OpenClaw project.
| Tool | Minimum Version | Purpose |
|---|---|---|
| Node.js | v22+ | Runtime |
| npm | v9+ | Package manager (workspaces) |
| Docker | Latest | Container build/test |
| AWS CLI | v2 | CDK deployment, resource inspection |
| AWS CDK CLI | v2.170+ | Infrastructure deployment |
git clone https://github.com/<owner>/serverless-openclaw.git
cd serverless-openclaw
# Install dependencies (all packages)
npm install
# TypeScript build
npm run build
# Git hooks setup (husky — runs automatically with npm install)
# pre-commit: build + lint + unit tests
# pre-push: E2E testsCopy the example file and set your AWS profile:
cp .env.example .env
# Edit .env:
# AWS_PROFILE=your-aws-profile-name
# AWS_REGION=ap-northeast-2Load before running CDK or AWS CLI commands:
export $(cat .env | xargs)
.envis in.gitignoreand will not be committed. See.env.examplefor the template.
serverless-openclaw/
├── packages/
│ ├── shared/ # Shared types, constants (TABLE_NAMES, BRIDGE_PORT, etc.)
│ ├── gateway/ # 7 Lambda handlers + 9 services
│ ├── container/ # Fargate container (Bridge server + OpenClaw client)
│ ├── lambda-agent/ # Lambda Container Image (OpenClaw agent runtime)
│ ├── web/ # React SPA (Vite + TypeScript)
│ └── cdk/ # AWS CDK infrastructure definitions (9 stacks)
├── docs/ # Design/deployment/development docs
├── scripts/ # Deployment helper scripts
├── references/ # Reference projects (excluded from build/test)
├── vitest.config.ts # Unit test config
└── vitest.e2e.config.ts # E2E test config
shared ← gateway
shared ← container
shared ← cdk
web (Vite bundler resolves shared directly)
Managed as an npm workspaces monorepo + TypeScript project references. Inter-package dependencies use "*" ("workspace:*" is pnpm-specific).
| Command | Description |
|---|---|
npm run build |
TypeScript build (tsc --build, all packages) |
npm run lint |
ESLint check (packages/**/*.ts) |
npm run format |
Prettier formatting |
npm run test |
Unit tests (vitest) |
npm run test:e2e |
E2E tests (vitest, *.e2e.test.ts) |
cd packages/cdk
npx cdk synth # Generate CloudFormation templates
npx cdk diff # Preview changes
npx cdk deploy # Deploy to AWS
npx cdk destroy # Delete resourcesDefines shared types and constants. Since all other packages reference this, check the impact scope when making changes.
src/constants.ts— TABLE_NAMES, KEY_PREFIX, BRIDGE_PORT, timeouts, etc.src/types.ts— Shared type definitions
Consists of 7 Lambda handlers and 9 services.
packages/gateway/
├── src/
│ ├── handlers/ # ws-connect, ws-disconnect, ws-message,
│ │ # telegram-webhook, api-handler, watchdog, prewarm
│ ├── services/ # task-state, connections, conversations,
│ │ # container, message, telegram,
│ │ # identity, lambda-agent, secrets
│ └── index.ts # Handler re-export
└── __tests__/ # Unit tests (vitest)
DI pattern: All services receive an injected send function. This can be replaced with a mock in tests.
// Example: createTaskStateService(send)
const send = vi.fn();
const service = createTaskStateService(send);The Bridge server and OpenClaw client running on Fargate.
packages/container/
├── src/
│ ├── bridge.ts # HTTP server (Express, Bearer token auth)
│ ├── openclaw-client.ts # JSON-RPC 2.0 / MCP over WebSocket
│ └── lifecycle.ts # Container lifecycle management
├── Dockerfile
└── start-openclaw.sh
React SPA (Vite + TypeScript). Cognito SRP auth, WebSocket real-time communication.
packages/web/
├── src/
│ ├── components/ # Auth/, Chat/, Status/
│ ├── hooks/ # useAuth, useWebSocket
│ └── services/ # auth, websocket, api
├── vite.config.ts
└── index.html
Local development:
cd packages/web
# Configure .env.local (requires deployed AWS resources)
cat > .env.local << 'EOF'
VITE_WS_URL=wss://<api-id>.execute-api.<region>.amazonaws.com/prod
VITE_API_URL=https://<api-id>.execute-api.<region>.amazonaws.com
VITE_COGNITO_USER_POOL_ID=<user-pool-id>
VITE_COGNITO_CLIENT_ID=<client-id>
EOF
npx vite dev # http://localhost:5173Consists of 9 CDK stacks.
| Stack | Key Resources |
|---|---|
| SecretsStack | SSM SecureString parameters (5 secrets) |
| NetworkStack | VPC, public subnets, VPC Gateway Endpoints, Security Group |
| StorageStack | 5 DynamoDB tables, S3, ECR |
| AuthStack | Cognito User Pool, App Client |
| ComputeStack | ECS cluster, Fargate Task Definition |
| LambdaAgentStack | Lambda Container Image (DockerImageFunction, ARM64, 2048MB, 15min) |
| ApiStack | WebSocket API, HTTP API, 7 Lambda functions, EventBridge |
| WebStack | S3 (web assets), CloudFront (OAC) |
| MonitoringStack | CloudWatch Dashboard (6 rows, 10 custom metrics) |
Dependencies: SecretsStack + NetworkStack → StorageStack → {AuthStack, ComputeStack, LambdaAgentStack} → ApiStack → WebStack + MonitoringStack
All implementations except UI (web package) follow TDD.
- Write tests first — write a failing test
- Minimal implementation — write the minimum code to pass the test
- Refactor — clean up code (keeping tests passing)
# Run a specific test file
npx vitest run packages/gateway/__tests__/services/message.test.ts
# Watch mode
npx vitest packages/gateway/__tests__/services/message.test.tsvi.mockhoisting: usevi.hoisted()when referencing variables in module-level mocks- AWS SDK send binding: cast as
ddb.send.bind(ddb) as (cmd: any) => Promise<any> - Use DI pattern: inject
sendfunction as a mock
Managed with husky and configured automatically.
| Hook | Execution | Purpose |
|---|---|---|
pre-commit |
npm run build && npm run lint && npm run test |
Ensure build, lint, and unit tests pass |
pre-push |
npm run test:e2e |
Ensure E2E tests pass |
You can bypass hooks with the
--no-verifyflag, but this is not recommended as it may cause CI failures.
npm run build— TypeScript build succeedscd packages/web && npx vite build— web dist/ generated- Secrets Manager secrets exist (only for deployment)
- Create a stack file in
packages/cdk/lib/stacks/ - Add export to
packages/cdk/lib/stacks/index.ts - Create instance + wire dependencies in
packages/cdk/bin/app.ts - Add stack verification to E2E tests
- Target: ES2022
- Module: Node16 resolution
- strict mode enabled
.jsextension required in import paths (ESM)
// Good
import { TABLE_NAMES } from "@serverless-openclaw/shared/constants.js";
// Bad
import { TABLE_NAMES } from "@serverless-openclaw/shared/constants";- Follow ESLint + Prettier configuration
- Use
"*"for inter-package dependencies (npm workspaces) - Never hardcode environment variables/secrets in code
- Never hardcode S3 bucket names (CDK auto-generates them)
Mandatory rules to prevent cost explosion or security incidents:
- Never create NAT Gateways (
natGateways: 0) - Never create ALB or VPC Interface Endpoints
- DynamoDB must use
PAY_PER_REQUESTonly - userId must only be generated server-side (IDOR prevention)
- Use
capacityProviderStrategyinstead oflaunchTypefor RunTask calls
Details: see CLAUDE.md