Skip to content

Binary engine fails to restart after a crash once its datamodel temp file is gone — subsequent queries hang forever (classic client 6.x) #30182

Description

@luochen1990

Package and version

@prisma/client@6.19.3 (classic prisma-client-js generator, engineType = "binary"). A commenter on #23318 reports the same panic with 5.3.1, so this does not look new in 6.x.

What happened?

With the binary engine, @prisma/client/runtime/binary.js writes the schema to a temp file once, at engine start (abridged from the minified bundle):

this.datamodelPath = <tmpdir>/<hash>.prisma
writeFileSync(this.datamodelPath, e.inlineSchema)

It then passes that path to the spawned query-engine process via --datamodel. The client keeps inlineSchema in memory but does not rewrite the file when respawning the engine.

If, over the lifetime of a long-running process, that temp file disappears — /tmp age-based cleanup being the usual suspect (systemd-tmpfiles policies, which also apply inside PrivateTmp namespaces, and container ephemeral tmp) — then:

  1. Nothing happens at first: the running engine keeps serving queries (it appears to read the datamodel only at startup), so the service stays healthy.
  2. When the engine process later dies once — a crash, an OOM kill, anything — the client cannot bring it back: the respawned engine process fails to open the datamodel file and panics:
Query engine exited with code 101
thread 'tokio-runtime-worker' (…) panicked at query-engine/query-engine/src/opt.rs:253:53:
Could not open datamodel file "/tmp/88e4da94feb9a89f0c5fcf1ba4acc41a.prisma"
  1. From that point every query fails or — worse — hangs forever without any further error output, while the Node process stays alive. Depending on timing we observed two variants: if a query was in flight when the engine died, it rejects once with a PrismaClientInitializationError and everything after hangs; if the engine died between two queries, there is no error at all, queries just silently never settle.

We hit what is very likely the same failure in production on 2026-08-31: a self-hosted Linkwarden instance (Next.js + Prisma, NixOS, systemd PrivateTmp=true) had been running for 12 days when every DB-backed request started hanging until the reverse proxy timed out (504s for ~40 minutes until we restarted the service). The service journal captured the same panic signature — thread panicked at query-engine/query-engine/src/opt.rs:253: Could not open datamodel file "/tmp/<hash>.prisma", with two different hashes (presumably multiple client instances) repeating every second, i.e. the client kept respawning the engine into the same panic with no backoff. What we could not establish is what removed the temp file in the first place. (Production ran client 6.10.1 with engines 6.19.3 — a skew introduced by the NixOS packaging, which swaps in a shared prisma-engines build; the reproduction below is an aligned 6.19.3 pair.)

What did you expect to happen?

When respawning the engine, the client should notice the datamodel file is missing and simply write it again — it already holds inlineSchema in memory, so rewriting datamodelPath before spawning the engine would fully self-heal. Recovery from an engine crash should not depend on the lifetime of a file in /tmp.

Minimal reproduction

Setup:

$ npm init -y && npm i prisma@6.19.3 @prisma/client@6.19.3

prisma/schema.prisma (SQLite, no rows needed; file:./dev.db resolves relative to it):

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

model User {
  id    Int    @id @default(autoincrement())
  email String @unique
}

repro.js:

const { PrismaClient } = require('@prisma/client')
const p = new PrismaClient()
async function tick() {
  try {
    const u = await p.user.findFirst()
    console.log(new Date().toISOString(), 'OK', u === null ? '(no rows)' : u.id)
  } catch (e) {
    console.log(new Date().toISOString(), 'FAIL:', e.name, String(e.message).slice(0, 300))
  }
}
tick()
setInterval(tick, 2000)

Steps (engine type must be binary — it is baked in at generate time):

$ PRISMA_CLIENT_ENGINE_TYPE=binary npx prisma db push
$ PRISMA_CLIENT_ENGINE_TYPE=binary npx prisma generate
$ node repro.js &
2026-08-31T15:02:36.295Z OK (no rows)
2026-08-31T15:02:38.306Z OK (no rows)

$ ls ${TMPDIR:-/tmp}/*.prisma   # a file appeared (hash varies per environment/run)
a94c9dd6e7de437c1db9a54150fa1bfd.prisma

$ rm ${TMPDIR:-/tmp}/a94c9dd6e7de437c1db9a54150fa1bfd.prisma   # simulate /tmp cleanup (use the hash you observed)
$ # … queries keep succeeding — the running engine does not need the file:
2026-08-31T15:02:44.329Z OK (no rows)
2026-08-31T15:02:46.328Z OK (no rows)
2026-08-31T15:02:48.338Z OK (no rows)

$ kill <query-engine pid>   # SIGTERM to the `query-engine` child of the node process
                            # (e.g. pgrep -f query-engine)

Now one of two things happens — both terminal:

# Variant A (captured in a separate run) — a query was in flight:
2026-08-31T14:57:24.266Z FAIL: PrismaClientInitializationError
Invalid `p.user.findFirst()` invocation …
Query engine exited with code 101
thread 'tokio-runtime-worker' panicked at query-engine/query-engine/src/opt.rs:253:53:
Could not open datamodel file "/tmp/88e4da94feb9a89f0c5fcf1ba4acc41a.prisma"

# Variant B — the engine died between two queries (from the run above):
# (no output at all, ever again — the OK ticks just stop)
2026-08-31T15:02:44.329Z OK (no rows)
2026-08-31T15:02:46.328Z OK (no rows)
2026-08-31T15:02:48.338Z OK (no rows)      # ← last line; process still alive

# In both variants: the node process stays alive, the engine is never
# respawned successfully, and every subsequent query hangs. Only restarting
# the process (which rewrites the temp file) recovers.

Note that deleting the temp file alone is harmless — the failure only manifests when the engine process dies, which is what makes this hard to attribute in production.

Environment

  • Node: v24.19.0
  • OS: NixOS (Linux x86_64)
  • Package manager: npm 11.17.0
  • Database: SQLite (in production: PostgreSQL 17)
  • Prisma: prisma / @prisma/client 6.19.3, binary engine (PRISMA_CLIENT_ENGINE_TYPE=binary, engines 6.19.3); production incident on client 6.10.1 / engines 6.19.3

Additional context

  • Same panic signature as Query engine exited with code 101, in Azure Functions #23318 (open since Feb 2024, Azure Functions): there the datamodel file became unreadable in a Windows sandbox, while in our reproduction the file is deleted outright — the shared root appears to be that respawning the binary engine depends on a datamodel file that may no longer be openable or exist, and there is no recovery path. (The default library engine appears unaffected — it does not spawn a process with a --datamodel file path — though we only verified the binary path.)
  • Suggested fix direction: on engine (re)start, check datamodelPath and rewrite it from inlineSchema if missing/unreadable (the client already owns both).
  • Note: this report is about the classic prisma-client-js client with the external binary engine, which is what our production app (Linkwarden) and apparently the Azure Functions reporters run; I couldn't find an equivalent external-engine code path in the Prisma Next packages, but the classic client is still the documented stable option for many deployments. If this code path is considered legacy/frozen, a wontfix with a pointer to the recommended migration path would still be helpful.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions