Backend
Type errors after a pull: a model or field does not exist on PrismaClient
Type errors after a pull: a model or field does not exist on PrismaClient
pnpm typecheck in the API reports errors such as Property 'someModel' does not exist on type 'PrismaClient', a missing queue name on PerformQueue, or a missing method on the SmartSend client. The code on disk looks correct.Cause. Stale dist folders. The API resolves @perform/* imports to package source, but @perform/db re-exports from @repo/database, which is consumed through its built dist. Other packages are also resolved through dist at runtime. After a pull that changed a schema or a package, the old build output is still there.Fix.pnpm build. These builds do not change tracked files.The API exits immediately with 'config validation failed'
The API exits immediately with 'config validation failed'
defineEnv prints one line per failing key and exits with code 78.Fix. Read the printed keys. The required ones are DATABASE_URL, REDIS_URL, BETTER_AUTH_URL, BETTER_AUTH_SECRET, SERVICE_AUTH_SECRET and SMARTSEND_BASE_URL. The two secrets need at least 16 characters, and the two URLs must parse as URLs.Also check where the .env file is. dotenv loads it from the current working directory. pnpm dev:api runs in apps/core-api, so the file must be there, not only in the repository root. See Local setup.The API starts on port 8000 and the web app cannot find it
The API starts on port 8000 and the web app cannot find it
PORT is not set. The schema default is 8000. Every script in the workspace expects 3031.Fix. Set PORT=3031 in the backend env.Port 3031 is already in use
Port 3031 is already in use
tools/ensure-core-api.sh starts the backend in the background when you run pnpm dev in the web repository, and that process outlives the terminal.Fix./tmp/perform-core-api-dev.log.Cannot find module '@repo/database' or another workspace package at startup
Cannot find module '@repo/database' or another workspace package at startup
dist folders.Fix. pnpm db:generate && pnpm build.A query fails with 'column does not exist' while types pass
A query fails with 'column does not exist' while types pass
pnpm db:migrate:status, then pnpm db:migrate. Check which database DATABASE_URL points to before you run it. See Database migrations.A migration hangs, then fails with an advisory lock timeout
A migration hangs, then fails with an advisory lock timeout
prisma migrate takes a session-level advisory lock. If an earlier run went through a connection pooler, the pooler can keep the backend connection, and the lock, after the process has exited.Fix. Run migrations over a direct connection. packages/db/prisma.config.ts uses DIRECT_URL for this. Set it explicitly if your pooler host does not follow the -pooler. naming the config strips. To clear a stuck lock, end the database session that holds it. Get agreement before terminating sessions on a shared database./readyz returns degraded: redis
/readyz returns degraded: redis
REDIS_URL. Readiness treats Redis as optional, so the API still answers.Effect. Queues and schedulers do not run, and signed requests cannot be checked for replay.Fix. Start Redis, or correct the URL.Every request from one office gets 429
Every request from one office gets 429
app.ts sets trust proxy to 1, which assumes exactly one reverse proxy in front of the API. With a different number of hops, req.ip is the proxy’s address or a spoofable value, and the /v1 limiter puts many clients in one bucket.Fix. Match the number of proxy hops to the setting. Locally there is no proxy and this does not occur.The commit hook fails on prisma.config.ts with no-direct-process-env
The commit hook fails on prisma.config.ts with no-direct-process-env
prisma.config.ts sits at a package root and reads process.env on purpose, because the Prisma CLI has no access to @perform/config. pnpm lint only covers src, so it passes.Fix. There is no clean one in the repository yet. Avoid staging that file with unrelated work, and raise it with the team instead of weakening the rule ad hoc.ESLint fails with 'Em dash (U+2014) is forbidden'
ESLint fails with 'Em dash (U+2014) is forbidden'
pnpm lint:em-dash lists the files.A dependency update is rejected with 'uses ^; pin to an exact version'
A dependency update is rejected with 'uses ^; pin to an exact version'
no-loose-version-pins rule. Backend manifests use exact versions.Fix. pnpm add --save-exact NAME@VERSION.Trainee login says WhatsApp is not configured
Trainee login says WhatsApp is not configured
TRAINEE_OTP_DEV_MODE=true in the backend env. The code is then logged and returned as devCode. Never do this in production.Web app
pnpm dev prints 'Timed out starting core-api'
pnpm dev prints 'Timed out starting core-api'
/healthz within 60 seconds. The script prints the last 40 lines of the backend log.Fix. Read those lines. It is usually a config validation failure, a missing build, or an unreachable database. Start the backend in its own terminal to see the full output.Pages load but every data call returns 401
Pages load but every data call returns 401
SERVICE_AUTH_SECRET is different in the two repositories, CORE_API_SERVICE_ID is not web, or the machine clock is far off. Signed requests carry a timestamp and the API rejects stale ones with stale or invalid timestamp.Fix. Copy the secret from the backend env. Check the system clock.The home page shows a connection error at / in development
The home page shows a connection error at / in development
proxy.ts rewrites / to the marketing dev server on port 3001. pnpm dev starts it together with the app. If you started only the saas app, nothing is listening there.Fix. Run pnpm dev from the repository root, or open /login directly.Port 3000 or 3001 is already in use
Port 3000 or 3001 is already in use
A change does not show up, or the dev server serves an old module
A change does not show up, or the dev server serves an old module
apps/saas/.next, and start again. pnpm clean runs turbo clean for a full reset.The dev server refuses requests from a phone on the same network
The dev server refuses requests from a phone on the same network
http://192.168.1.20:3000, to AUTH_TRUSTED_ORIGINS. next.config.ts turns that list into allowedDevOrigins. The backend reads the same variable for auth and CORS.The commit or push is blocked by check-theme-tokens
The commit or push is blocked by check-theme-tokens
bg-background, bg-card, text-foreground, text-muted-foreground, border-border). Run pnpm check:theme to list every hit. A file that paints the mobile app’s own palette belongs on the allow list at the top of tools/check-theme-tokens.mjs, with a comment saying why.Uploads above a certain size fail
Uploads above a certain size fail
next.config.ts sets proxyClientMaxBodySize and the Server Action bodySizeLimit to 24mb. The API’s JSON parser accepts 24mb in general, 48mb on the two AI analyze routes, 4mb on the public signing route, and 12mb for raw application/octet-stream parts.Fix. Large media goes to object storage with a presigned URL or the chunked upload path, not through a JSON body.Hydration warnings mentioning a drag and drop id
Hydration warnings mentioning a drag and drop id
DndContext without a stable id generates different ids on the server and the client.Fix. Pass id={useId()} to the DndContext.Mobile app
The app cannot reach the API from a physical device
The app cannot reach the API from a physical device
localhost. In development the app derives the host from Metro, which normally gives your machine’s LAN address.Fix. Look for the [perform] API ... line in the Metro output to see which URL is in use. Run pnpm use-lan-ip to write the LAN address into .env, then restart with pnpm start -- --clear. The phone and the computer must be on the same network, and the API must be listening on port 3031. A VPN on either device often breaks this.A changed EXPO_PUBLIC_ value is ignored
A changed EXPO_PUBLIC_ value is ignored
pnpm start -- --clear.A module is missing in Expo Go
A module is missing in Expo Go
pnpm ios or pnpm android, then pnpm dev:device.TypeScript rejects a route string that exists
TypeScript rejects a route string that exists
.expo/types. The file is stale after a route was added or renamed.Fix. Start Metro once so the types regenerate. Do not cast the route string.pnpm install fails or a patch does not apply
pnpm install fails or a patch does not apply
patches/.Fix. nvm use to get Node 24.14.0, and use the pnpm version from packageManager. If a patched dependency changed version, recreate the patch for the new version and update patchedDependencies in pnpm-workspace.yaml.The Android build fails in Gradle while downloading dependencies
The Android build fails in Gradle while downloading dependencies
pnpm prebuild:clean and build again.A fix was published but a device still shows the old behaviour
A fix was published but a device still shows the old behaviour
The layout direction is wrong after changing the language
The layout direction is wrong after changing the language
applyAppDirection() reloads the app once after a language change to pick it up.Fix. Relaunch the app. If the direction is wrong in Expo Go only, that is expected. Test on a development build.All repositories
Wrong Node version
Wrong Node version
engines, syntax errors from tools, or native module build failures.Fix. Use Node 24.14.0 for the whole workspace. It satisfies the backend (>=22), the web app (>=20) and the mobile app’s .nvmrc. Run nvm use inside backend or mobile to follow their .nvmrc.Wrong pnpm version
Wrong pnpm version
--frozen-lockfile failing.Fix. Run corepack enable. Corepack reads packageManager in each repository and uses pnpm 11.5.0 in the backend and 10.28.2 in the web and mobile repositories.Git hooks do not run
Git hooks do not run
prepare script during install. A clone that was never installed, or an install with scripts disabled, has none.Fix. Run pnpm install in that repository.commitlint rejects the message
commitlint rejects the message
type(scope): subject with one of the allowed types, a header of at most 100 characters, and a lower case or sentence case subject. See Branches and commits.The machine slows to a crawl during checks
The machine slows to a crawl during checks
--maxWorkers=2 for Vitest and --workers=2 for Playwright.