Skip to content

api images built from current main crash at boot: libnest banner __dirname collision with @prisma/client ESM runtime #1491

Description

@gdevenyi

Summary

Every api docker image built from current main crashes at boot. The bundled app.js dies during module evaluation, before Nest starts:

file:///app/dist/app.js:226145
globalThis["__dirname"] = __banner_node_path.dirname(__filename2);
                        ^
TypeError: Cannot assign to read only property '__dirname' of object '#<Object>'

The published ghcr.io/…/open-data-capture-api:2.1.6 image is not affected (verified: it boots to the point of querying mongo). The next release built from main as it stands will ship a container that cannot start.

Root cause

Two banners fight over globalThis.__dirname. Upstream issue with full analysis and a verified fix: DouglasNeuroInformatics/libnest#77.

  1. libnest's build banner (line 1 of dist/app.js, present in 8.3.1 and 8.4.0, src/meta/build.ts:66) defines globalThis.__dirname / __filename with writable: false.
  2. @prisma/client@6.19.3's ESM runtime (runtime/library.mjs) opens with its own banner that assigns globalThis['__dirname'] = …. When the bundle inlines that runtime, the strict-mode assignment hits the non-writable property and throws.

Why it appears now

Dependency pins are unchanged — v2.1.6 and today's main both lock libnest 8.3.1 and prisma 6.19.3, and the 2.1.6 bundle contains zero __banner_node_path markers while a build of 38fa109 contains the inlined .mjs runtime. Something in the v2.1.6..38fa109 window (predominantly the mailer split, #1439) changed the import graph so esbuild now resolves @prisma/client through its import condition and pulls runtime/library.mjs into the bundle. The exact import site was not bisected; it also does not matter for the fix — any future graph change could re-trigger it, so the banner must tolerate the assignment.

Attribution evidence

  • Control build at merge base 38fa109 (no open-PR code): crashes identically. PR fix(instrument-bundler): keep the esbuild switch alive through tree shaking #1482 explicitly exonerated — noted in the review posted on that PR.
  • Crash is Node-version-independent within the image's range (24.18.0, 24.19.0; node:lts-krypton currently floats to 24.19.0).
  • Rebuilding with only the banner flipped to writable: true boots and runs the full setup + default instrument-repo import (24/24 instruments) against a mongo replica set.

Path to green

  1. Fix in libnest (build banner defines non-writable globalThis.__dirname, crashing bundles that inline @prisma/client's ESM runtime libnest#77 — one-line writable: true, verified), release, bump here (@douglasneuroinformatics/* is in minimumReleaseAgeExclude, so the bump can land immediately).
  2. Add a release-blocking check that the built api image actually boots — this failure is invisible to lint, unit tests, and the e2e suite (which runs the never-bundled dev server), and was only caught by running the container.

Repro

docker build -f apps/api/Dockerfile --build-arg RELEASE_VERSION=9.9.9 -t odc-api-repro .
docker run --rm -e NODE_ENV=production -e MONGO_URI=mongodb://localhost:27017 \
  -e SECRET_KEY=$(openssl rand -hex 32) -e GATEWAY_API_KEY=$(openssl rand -hex 32) \
  -e GATEWAY_ENABLED=false -e GATEWAY_REFRESH_INTERVAL=10000 \
  -e GATEWAY_SITE_ADDRESS=http://localhost:3500 -e STORAGE_ENABLED=false odc-api-repro
# → TypeError before any log output; compare ghcr.io/douglasneuroinformatics/open-data-capture-api:2.1.6, which boots

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions