diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3475e87..99535f8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,11 @@ jobs: # `build:bot` actually ships, and the landing build was not run at all. # build:all covers both plus the frontend build this step used to do. - run: npm run build:all + # The image installs `npm ci --omit=dev` (Dockerfile) and boots + # `tsx ./src/main.ts`, so a runtime dep that drifts into devDependencies + # boots fine everywhere except production. Runs last: it wipes dev deps. + - run: npm ci --omit=dev + - run: ./node_modules/.bin/tsx --version backend-tests: runs-on: ubuntu-latest diff --git a/CLAUDE.md b/CLAUDE.md index 79a6e91..6fd3dd8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,6 +51,7 @@ npm run smoke:api # boots the REAL app (STAGING, - **Fatal startup errors**: the top-level catch in `main.ts` uses `console.error`, not `logger` — pino's worker-thread transport can't flush before `process.exit(1)`. - **`BOT_WEBHOOK_SECRET`** is only required in webhook mode (post-parse check in `config.ts`); empty is valid in polling mode. - **Model index conventions**: `unique: true` alone (it creates the index); single-field secondary indexes go in class-level `@index()` decorators; schemaless `meta` bags need `options: { allowMixed: Severity.ALLOW }`. +- **The image is a prod-only install**: `Dockerfile` runs `npm ci --omit=dev` and boots `tsx ./src/main.ts`, so **tsx is a runtime dependency**. Never `npm i -D` a package that already lives in `dependencies` — npm moves it, every local check still passes (they install dev deps), and the container crash-loops on deploy. The last two steps of the `quality` CI job catch it. - **bigint in responses**: fastify's JSON serializer throws on bigint — always `.toString()` votes/costs in handler responses. ## Handler Pattern (DI)