Skip to content

Troubleshooting

Ankit Upadhyay edited this page Aug 13, 2026 · 1 revision

Troubleshooting

This page collects the traps that actually cost people time in this repository, with the fix for each. It is for when something does not work and the error message is not enough.

Install and toolchain

pnpm install fails with an engine error

.npmrc sets engine-strict=true and the root package.json requires Node 22.12 or later. A different major fails the install rather than warning.

node --version    # must be 22.x
nvm use           # reads .nvmrc
corepack enable   # installs the pinned pnpm, not a global one

Do not install pnpm globally. The version is pinned in packageManager, and CI reads that field, so a global pnpm is how local and CI diverge.

pnpm install seems to hang, or refuses a version that exists

pnpm-workspace.yaml sets minimumReleaseAge: 4320, which refuses any package version published in the last three days. This is a deliberate supply-chain guard. Wait, or pin to an older version. Do not remove the setting to unblock yourself.

A package's postinstall did not run

pnpm 10 blocks postinstall scripts unless the package is on the onlyBuiltDependencies list in pnpm-workspace.yaml. If a new dependency genuinely needs one, add it there in the same pull request and say why.

Prisma

Type errors everywhere in packages/database or apps/api

The generated client is not in the repository, so a fresh clone has no Prisma types.

pnpm --filter @openrunic/database exec prisma generate

This is the single most common first-day failure. CI generates the client explicitly before the package build and before any test leg that needs it, for exactly this reason.

prisma migrate cannot find a database URL

Prisma reads a package-local .env, not a root one.

cp packages/database/.env.example packages/database/.env

Prisma 7's CLI no longer auto-loads .env, so packages/database/prisma.config.ts loads it explicitly. That config prefers DIRECT_URL over DATABASE_URL, because migration commands must go straight at Postgres rather than through a pooler. It omits the datasource entirely when no URL is set, which is what keeps prisma generate and prisma validate working with no database at all.

CI fails with "an existing migration was modified"

You edited an applied migration. Revert the edit and add a new migration instead. See Upgrades and migrations.

Note that a rename counts as a delete plus an add, and the check runs with renames disabled specifically to catch that.

CI fails with "schema.prisma and prisma/migrations have drifted apart"

Either you changed the schema without generating a migration, or you changed a migration without updating the schema. Reproduce locally:

pnpm --filter @openrunic/database exec prisma migrate diff \
  --from-migrations ./prisma/migrations \
  --to-schema ./prisma/schema.prisma --script

Anything but comments and blank lines is the drift.

Do not run prisma format

It reflows the whole schema file and produces a diff nobody can review alongside a real change.

The web app

Screens show data but nothing saves

Expected. The mock layer deliberately implements no writes, because a fixture that accepts writes teaches screens to trust state the server never saw. Every screen that appears to write holds the change in component state and usually says so.

NEXT_PUBLIC_API_MODE=live did not change the screen

Three of five data clients have no live path at all. Admin, reports, orders, results, inbox, and all five billing screens stay on fixtures in either mode, because the API answers 501 for those aggregates. Only patients, appointments, and the chart switch.

Live mode returns 401 on everything

Also expected. Authentication is not wired, so the client sends no Authorization header. The screens render that honestly through their error state rather than falling back to fixtures.

An environment variable change had no effect

NEXT_PUBLIC_ variables are inlined by Next at build time and resolved once at module load. Restart the dev server. And they must be written as literal member expressions in source for the inlining to work; a dynamic lookup will not be replaced.

Coverage reports half of what it should

Do not add @vitejs/plugin-react to a vitest config. Its second transform pass double-instruments files under istanbul and roughly halves the numbers. Both apps use esbuild's automatic JSX runtime instead. The plugin belongs in the Vite build config and in Storybook, not in the test config.

Fonts and logos are missing

public/fonts/ and public/assets/logo/ are not in git, by policy. The fallback stack carries the interface; only the optical-size axis is lost. Nothing is broken.

CI

Validate PR title fails but commitlint passed locally

The scope is required at the PR level and optional for commits. feat: add patient search passes every local hook and reds the pull request. Write feat(web): add patient search.

Note also that the comment block in the pull request template omits ui from its scope list. commitlint.config.cjs is authoritative and does include it.

CI ran everything for a one-line change

Detection widens to the whole repository whenever it cannot answer "what changed" with confidence: no trustworthy base SHA, a failed turbo dry run, a change to the lockfile or root configuration, or a change under .github/workflows/, .github/actions/, or scripts/ci/. That last one is deliberate: a pull request that only edits the pipeline would otherwise pass without exercising the pipeline code it changed.

The pull request is green but Sonar never ran

The Sonar stage is skipped while DISABLE_SONAR is set, and the aggregate counts a skipped stage as a pass. A green pull request today has not been Sonar-scanned. See Code quality bar.

Re-running after a force push produces a wrong base

ci.yaml accepts a force_push_base input on manual dispatch for exactly this. Supply the correct base SHA.

Worktrees

Bare npx tsc or npx eslint behaves oddly in a worktree

Resolution can pick up the wrong binary. Use the workspace scripts:

pnpm --filter <workspace> type-check
pnpm --filter <workspace> lint

Two processes fighting over one worktree

Run one git process per worktree. A background job that reported finished may still have children touching the index.

Concurrent work collides in the same files

Use a dedicated worktree per workstream. Three files are high-collision by construction and worth coordinating on: packages/database/prisma/schema.prisma, packages/ui/src/index.ts, and packages/ui/src/components/_index.css. The two library registries are organised one entry per line, alphabetical, specifically so parallel edits never touch the same line.

Runtime

The built API throws immediately on start

assertProductionWiring throws when NODE_ENV is production and the repositories, principal resolver, and audit sink were not supplied explicitly. apps/api/src/index.ts constructs the app with in-memory defaults, so pnpm start in production mode fails by design rather than serving fixtures.

A route returns 501 with a role that should work

That is the reserved-aggregate behaviour and it is correct. Seven aggregates are mounted, authenticated, and authorised before returning 501. Reaching the 501 means your token and role were fine.

A record exists but reads as 404

Probably a cross-tenant read. That is reported as 404 rather than 403 on purpose, because a 403 would confirm the id exists somewhere. Check which tenant your token belongs to; the demo resolver's fourth token is in a second tenant precisely so this is easy to try.

$queryRaw throws

The tenant-scoped client blocks all four raw methods. There is no string-SQL path through it, and that is a structural property rather than an oversight. If you need something the query API cannot express, that is a design discussion, not a workaround.

Related pages

Clone this wiki locally