Repository navigation
Build System
Canonical source:
docs/08-runtime-devops/build-system.md— this page is rendered from it on every publish; edit the source.
Audience: any engineer or AI agent who needs to build, run, typecheck, or debug a service. This is the canonical reference for how ClawAI compiles.
ClawAI does not use nest build to compile services. It compiles with
TypeScript 7 — the Go-native TypeScript compiler, installed at the root as
the typescript7 alias ("typescript7": "npm:typescript@7.x") and invoked
through tools/typescript/run-ts7.mjs —
plus tsc-alias to rewrite path aliases after compilation. This applies to
**all 21 backend services AND all
(claw-frontend) typechecks with TypeScript 7 (apps/claw-frontend/scripts/typecheck-ts7.mjs) and
bundles/tests with Next.js + Vitest.
You never call the compiler directly: npm run build / npm run typecheck in a
workspace wrap it, and the git hooks run them for you
(rules/23).
-
Speed. TypeScript 7 is the native (Go) port of the TypeScript compiler;
build and typecheck are dramatically faster than the JavaScript
tsc6.x on this monorepo. - One compiler everywhere. Services and shared packages share the same toolchain, so behaviour is consistent across CI, Docker, and local dev.
TypeScript 7 is stricter about module resolution than the old tsc setup (see
§7 Gotchas).
Two TypeScript versions coexist on purpose:
-
typescript7is what compiles.tools/typescript/run-ts7.mjsresolvestypescript7/package.jsonand runs itsbin/tscwith the arguments it was given, so a workspace script readsnode ../../tools/typescript/run-ts7.mjs -p tsconfig.build.json. -
typescript6.x stays for tools that import the TypeScript JS API (typescript-eslint, editors). Each workspace also pins it as a devDependency next totsc-alias. - CI's setup job checks the compiler with
node tools/typescript/run-ts7.mjs --versionbefore anything else runs.
The compiler emits .ts → .js but does not rewrite path aliases. After it
runs, dist/ still contains imports of @app/... / @common/... / @modules/....
tsc-alias -f reads the paths map from tsconfig.build.json, rewrites those
to relative paths, and adds the .js extensions ESM needs, so
node dist/main.js resolves with no runtime path-mapping loader. Prisma-backed
services run tools/typescript/copy-generated-prisma.mjs between the compile and
the tsc-alias pass, so dist/generated/prisma exists when the extensions are
resolved.
Backend services (apps/claw-auth-service/package.json is representative):
| Script | Command |
|---|---|
dev |
nodemon --legacy-watch --watch src --ext ts,json --delay 1500ms --signal SIGTERM --exec "npm run build && node dist/main.js" |
build |
node ../../tools/typescript/run-ts7.mjs -p tsconfig.build.json && node ../../tools/typescript/copy-generated-prisma.mjs && tsc-alias -p tsconfig.build.json -f |
typecheck |
node ../../tools/typescript/run-ts7.mjs --noEmit --incremental --tsBuildInfoFile node_modules/.cache/tsc/typecheck.tsbuildinfo |
start |
node dist/main.js |
dev watches src/ and, on every change, rebuilds and restarts node. The
1500ms nodemon delay debounces bursts of saves. (Services without Prisma omit the
copy-generated-prisma.mjs step.)
Shared packages (packages/*):
| Script | Command |
|---|---|
build |
node ../../tools/typescript/run-ts7.mjs -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f |
typecheck |
node ../../tools/typescript/run-ts7.mjs --noEmit --incremental --tsBuildInfoFile node_modules/.cache/tsc/typecheck.tsbuildinfo |
lint |
eslint src/ --cache --cache-strategy content --cache-location node_modules/.cache/eslint/ |
test |
vitest run (an empty suite fails) |
Each workspace has two configs:
-
tsconfig.json— the editor/typecheck config. Key options:module: Node16,moduleResolution: Node16,strict,paths(@app/*,@common/*,@modules/*,@infrastructure/*),types: ["node"](Vitest globals come fromsrc/vitest-globals.d.ts). NobaseUrl, nomoduleResolution: node, noignoreDeprecations— TypeScript 7 rejects those. -
tsconfig.build.json— extendstsconfig.json, setssourceMap: false, and excludes specs:["node_modules", "dist", "test", "**/*.spec.ts", "**/*.e2e-spec.ts", "**/__tests__/**"].build/devuse this;typecheckusestsconfig.json(so specs are typechecked too).
All images use node:26-bookworm-slim (Debian/glibc), never Alpine:
the native compiler and llama.cpp release binaries are glibc-linked and will not
run on musl. Dev Dockerfile.dev installs deps at the workspace root
(npm install --ignore-scripts), builds each shared package the service uses
with RUN cd packages/<pkg> && npm run build, runs prisma generate, then the
entrypoint runs migrations and npm run dev. Prod Dockerfiles are multi-stage
(base → deps → builder → runner); the builder runs npm run build, the runner
copies dist/ + node_modules and runs node dist/main.js (or a prisma-aware
entrypoint). claw-file-service and claw-llamacpp-service drop privileges via
runuser (Debian's su-exec equivalent). See docker-guide.md.
A setup job installs once (npm ci --ignore-scripts, cached), verifies the
compiler (node tools/typescript/run-ts7.mjs --version), builds the shared
packages in dependency order (node tools/affected/index.mjs shared-build — the
order is derived from each package.json, never hard-coded), generates every
Prisma client, and uploads packages/*/dist + apps/*/src/generated as an
artifact. The per-workspace jobs (static = lint ratchet + typecheck, test, build)
run over the affected matrix only and restore that artifact through
.github/actions/prepare. Job by job:
ci-cd-pipeline.md.
Every FROM node: stage in every Dockerfile is followed by one guard RUN that fails the build when
the base image is older than the fixed Node versions of the 2026-10-21 security release (22.23.4,
24.22.1, 26.12.1). The floors live in tools/security/node-runtime-floor.mjs; root package.json
engines is the same range. deploy-prod.sh builds with --pull. To raise a floor, follow
runbook-node-runtime-floor.
| Symptom | Cause / fix |
|---|---|
Cannot find module '@claw/shared-<x>' while building a service image |
Declare the workspace in the service package.json, then build it in both Dockerfile and Dockerfile.dev. tools/__tests__/dockerfile-shared-package-completeness.test.mjs checks declared packages against both image recipes. |
TS5108: option 'node' … deprecated or errors on baseUrl
|
Old tsconfig. TypeScript 7 requires moduleResolution: Node16/nodenext, relative paths, no baseUrl, no ignoreDeprecations. |
Cannot find module '../x' on a dynamic await import('../x')
|
Node16 needs an explicit .js extension for dynamic relative imports — or convert to a static import. |
| Third-party lib's named exports "do not exist" under Node16 | The lib publishes extensionless export * chains (e.g. docx) that Node16 can't traverse. Load it via dynamic import() through an unknown boundary + a local typed interface, or pin a Node16-friendly version. pdf-parse v2 is a class API (new PDFParse({data}).getText()), not the v1 function. |
Cannot find module 'typescript7/package.json' |
Dependencies are not installed at the root — typescript7 is a root devDependency. Run npm ci at the repository root; CI's setup job checks it with node tools/typescript/run-ts7.mjs --version. |
Cannot find module '@claw/<pkg>' in CI |
The service does not declare the package in its package.json, so it is missing from the shared-build order (node tools/affected/index.mjs shared-build) and from the CI build artifact. |
npm ci: "Missing X from lock file" on Linux |
A cross-platform optional binary was stripped. Ensure .npmrc has include=optional and regenerate the lockfile. |
Decorator metadata error (TS1272) |
isolatedModules: true + emitDecoratorMetadata conflict. Use isolatedModules: false (services already do). |
<lib>.<fn> is not a function at runtime, for a library imported as import * as x from '<lib>'
|
Every workspace is "type": "module". Under real ESM a CommonJS package's namespace holds only what cjs-module-lexer could statically detect; the real module.exports sits on .default. It is per-package: argon2 exposes everything, jsonwebtoken exposes decode alone, so jwt.verify was undefined and 401'd every request in prod on 2026-09-02. Use a default import (import jwt from 'jsonwebtoken'). Unit tests do not catch this (ts-jest used to transpile specs to CommonJS; Vitest's default deps.interopDefault can likewise mask it) — tools/__tests__/esm-namespace-import-bindings.test.mjs does. |
SyntaxError: The requested module '<lib>' does not provide an export named 'X' at boot |
A named import of a CommonJS export that cjs-module-lexer did not detect. Per-NAME, not per-package: mongoose re-exports Model and Schema but not Connection. Use import type { X } when it is only a type. What usually keeps the import alive at all is a constructor parameter typed with it — emitDecoratorMetadata writes the class into design:paramtypes, so a file that looks type-only still emits a runtime import. Guarded by tools/__tests__/esm-namespace-import-bindings.test.mjs. Took three Mongo services down, 2026-09-02. |
ReferenceError: Cannot access 'X' before initialization at boot |
A circular import. CommonJS tolerated it (a partially filled exports object); ESM leaves the binding in a temporal dead zone. forwardRef(() => X) is a lazy closure and is NOT the cause — the eager read is the design:paramtypes metadata for a constructor parameter typed as the class. Type that parameter with an interface from types/ so the metadata emits Object. See rule 13 §8. file-service, 2026-09-02. |
ERR_MODULE_NOT_FOUND …/packages/shared-<x>/dist/<file> (no .js) in a dev container only |
The image copied the host's stale packages/shared-<x>/dist (emitted before tsc-alias -f added .js extensions) because that service's Dockerfile.dev never builds shared-<x>. Add the RUN cd packages/shared-<x> && npm run build line to BOTH Dockerfiles; tools/__tests__/dockerfile-shared-package-completeness.test.mjs enforces it. See docker-guide.md. |
ReferenceError: __dirname is not defined in ES module scope at boot |
Every service package.json is "type": "module" (since the NestJS 12 upgrade), so dist/*.js is ESM and CommonJS globals (__dirname, __filename, require) do not exist. Use import.meta.dirname / import.meta.filename. Do not register tsconfig-paths at runtime — aliases are already rewritten by tsc-alias -f at build time, and tsconfig-paths only hooks CommonJS require anyway. chat-service crash-looped the 2026-09-02 prod rollout on exactly this; its ESLint config now bans both globals via no-restricted-globals. |
-
docker-guide.md — container lifecycle,
claw.sh - ci-cd-pipeline.md — full CI pipeline
- ../04-backend/shared-packages.md — the shared packages
- port-service-map.md — service ↔ port ↔ database map
-
npm run security:fixrunstools/security/audit-fix.mjs: it pins the patched release of any production dependency flagged bynpm audit, regenerates the lockfile, verifies, and rolls back on failure. Pre-commit and a daily workflow call it (ADR-202). - Root
overridespin transitive packages that carry an advisory (for exampleip-address^10.7.5, pulled in bygeoip-country, andfollow-redirects). Remove an override once the parent package ships the fix. -
geoip-country(apps/claw-auth-service) is the offline country lookup for the admin Users page (ADR-200): 8 MB, pure JavaScript, no install script.
ClawAI Wiki · source-controlled under wiki/ · generated/mirrored from the current repository knowledge layer.
ClawAI Wiki
Product
- Product-Vision
- Flagship-Features
- Business-Overview
- Requirements-Register
- Drift-Log
- Runtime-Managed-Connector-Providers
- Threads
- ADR-Index
Architecture
- System-at-a-Glance
- Architecture-Map
- System-Architecture
- Message-Flow
- End-to-End-Data-Flow
- Routing-Engine
- Event-Bus
- Security-Architecture
Platform
- Backend-Services
- Frontend-Architecture
- Shared-Packages
- Database-Reference
- API-Reference
- Provider-Catalog
- Memory-and-Context-Architecture
- File-Support-Architecture
- Research-and-Web-Crawling
Runtime & Ops
- Docker-Guide
- Nginx-Reference
- CI-CD-Pipeline
- Logging-and-Observability
- Operational-Runbooks
- Troubleshooting
Quality
Engineering OS
- AI-Native-Engineering-OS
- Generated-Manifests
- Rules-Catalog
- Skills-Catalog
- Context-Layer
- Engineering-Memory
- Tooling-and-Automation
- Scripts-and-Operations
- ESLint-and-Architecture-Enforcement
- Extended-Work-Skills
Agents
Reference