pnpm dev rejoue prisma generate à chaque démarrage : sur Windows il échoue en EPERM dès qu'une autre instance tient le moteur #24

Closed
opened 2026-09-12 08:27:49 +00:00 by nicolas-fryder · 2 comments

Le script dev d'apps/api enchaîne prisma generate avant de lancer le serveur. Sur Windows, le binaire du moteur de requête est verrouillé par tout processus Node qui l'a déjà chargé : relancer pnpm dev pendant qu'une instance tourne échoue systématiquement, avec un message qui ne nomme jamais Prisma ni la cause.

Reproduction

Dans un satellite, API déjà lancée dans un autre terminal :

pnpm --filter api dev

Sortie :

$ pnpm db:schema && prisma generate && concurrently -k -n types,serveur ...
$ node scripts/schema-auth.mjs
prisma/schema/auth.prisma copié depuis ...\@eqs\auth\schemas\auth.prisma
Loaded Prisma config from prisma.config.ts.
Prisma config detected, skipping environment variable loading.
Prisma schema loaded from prisma\schema
Error:
EPERM: operation not permitted, rename '...\.prisma\client\query_engine-windows.dll.node.tmp102972' -> '...\.prisma\client\query_engine-windows.dll.node'

[ELIFECYCLE] Command failed with exit code 1.

Le script fautif, apps/api/package.json :

"dev": "pnpm db:schema && prisma generate && concurrently -k -n types,serveur -c blue,green \"pnpm dev:types\" \"pnpm dev:serveur\"",

Ce que ça coûte

Non bloquant une fois qu'on sait — mais on ne le sait pas, et le message n'aide pas :

  1. Il n'y a aucune trace de la cause réelle dans le message. « EPERM … rename … .tmp102972 » sur un .dll.node ne dit pas « une autre instance tourne ». On cherche un problème de droits, un antivirus, un node_modules corrompu.
  2. C'est le cas NORMAL en développement, pas un cas limite : on relance l'API après avoir changé une variable d'environnement, après un git checkout, ou parce qu'un agent a redémarré la pile. La commande échoue, et l'API qui tourne encore laisse croire que tout va bien — jusqu'à ce qu'on cherche pourquoi le code modifié ne prend pas.
  3. Le vrai piège : dev:serveur utilise node --watch sur dist/, et dev:types recompile en continu. Le prisma generate en tête n'est nécessaire qu'au premier démarrage, ou après un changement de schéma — pas à chaque lancement.

Chez nous, ça a coûté deux redémarrages ratés dans la même session, dont un où j'ai cru avoir perdu le serveur alors qu'il tournait toujours.

Ce que je proposerais

Par ordre de préférence :

  1. Sortir prisma generate du script dev et le laisser où il est déjà : dans postinstall (il y est) et dans le script de migration. Le client Prisma est généré à l'installation et à chaque changement de schéma ; le régénérer à chaque dev ne protège de rien que postinstall ne couvre pas déjà.
  2. Si on tient à la garantie, la rendre conditionnelle et non fatale : un prisma generate || true masquerait l'échec mais garderait le bénéfice au premier lancement. Moins propre, mais une ligne.
  3. Au minimum, nommer la cause quand generate échoue en EPERM sur Windows : « le moteur Prisma est verrouillé — une autre instance de l'API tourne probablement ; arrêtez-la, ou lancez dev sans régénérer ». Un message qui nomme sa cause vaut mieux qu'un message exact.

Accessoirement, la même remarque vaut pour db:schema (la copie de auth.prisma) : elle est idempotente et rapide, donc inoffensive — mais elle s'exécute elle aussi à chaque démarrage sans que ce soit nécessaire.

Contournement posé chez nous

Aucun dans le dépôt. On relance à la main en tuant d'abord l'instance en cours, ce qui suppose de savoir qu'il faut le faire.

Versions

  • @eqs/cli, @eqs/auth, @eqs/testing : 0.8.1
  • Prisma / @prisma/client : 6.19.3
  • pnpm@11.8.0, Node v24.15.0, Windows 11 Pro 26200
  • Gabarit : apps/api/package.json, script dev

Trouvé en développant le premier satellite réel (Atelier, ex-Pilotage), en relançant l'API après un renommage.

Le script `dev` d'`apps/api` enchaîne `prisma generate` avant de lancer le serveur. Sur Windows, le binaire du moteur de requête est verrouillé par tout processus Node qui l'a déjà chargé : relancer `pnpm dev` pendant qu'une instance tourne **échoue systématiquement**, avec un message qui ne nomme jamais Prisma ni la cause. ## Reproduction Dans un satellite, API déjà lancée dans un autre terminal : ```bash pnpm --filter api dev ``` Sortie : ``` $ pnpm db:schema && prisma generate && concurrently -k -n types,serveur ... $ node scripts/schema-auth.mjs prisma/schema/auth.prisma copié depuis ...\@eqs\auth\schemas\auth.prisma Loaded Prisma config from prisma.config.ts. Prisma config detected, skipping environment variable loading. Prisma schema loaded from prisma\schema Error: EPERM: operation not permitted, rename '...\.prisma\client\query_engine-windows.dll.node.tmp102972' -> '...\.prisma\client\query_engine-windows.dll.node' [ELIFECYCLE] Command failed with exit code 1. ``` Le script fautif, `apps/api/package.json` : ```json "dev": "pnpm db:schema && prisma generate && concurrently -k -n types,serveur -c blue,green \"pnpm dev:types\" \"pnpm dev:serveur\"", ``` ## Ce que ça coûte Non bloquant une fois qu'on sait — mais on ne le sait pas, et le message n'aide pas : 1. **Il n'y a aucune trace de la cause réelle** dans le message. « EPERM … rename … .tmp102972 » sur un `.dll.node` ne dit pas « une autre instance tourne ». On cherche un problème de droits, un antivirus, un `node_modules` corrompu. 2. **C'est le cas NORMAL en développement**, pas un cas limite : on relance l'API après avoir changé une variable d'environnement, après un `git checkout`, ou parce qu'un agent a redémarré la pile. La commande échoue, et l'API qui tourne encore laisse croire que tout va bien — jusqu'à ce qu'on cherche pourquoi le code modifié ne prend pas. 3. Le vrai piège : `dev:serveur` utilise `node --watch` sur `dist/`, et `dev:types` recompile en continu. **Le `prisma generate` en tête n'est nécessaire qu'au premier démarrage**, ou après un changement de schéma — pas à chaque lancement. Chez nous, ça a coûté deux redémarrages ratés dans la même session, dont un où j'ai cru avoir perdu le serveur alors qu'il tournait toujours. ## Ce que je proposerais Par ordre de préférence : 1. **Sortir `prisma generate` du script `dev`** et le laisser où il est déjà : dans `postinstall` (il y est) et dans le script de migration. Le client Prisma est généré à l'installation et à chaque changement de schéma ; le régénérer à chaque `dev` ne protège de rien que `postinstall` ne couvre pas déjà. 2. Si on tient à la garantie, **la rendre conditionnelle et non fatale** : un `prisma generate || true` masquerait l'échec mais garderait le bénéfice au premier lancement. Moins propre, mais une ligne. 3. Au minimum, **nommer la cause** quand `generate` échoue en EPERM sur Windows : « le moteur Prisma est verrouillé — une autre instance de l'API tourne probablement ; arrêtez-la, ou lancez `dev` sans régénérer ». Un message qui nomme sa cause vaut mieux qu'un message exact. Accessoirement, la même remarque vaut pour `db:schema` (la copie de `auth.prisma`) : elle est idempotente et rapide, donc inoffensive — mais elle s'exécute elle aussi à chaque démarrage sans que ce soit nécessaire. ## Contournement posé chez nous Aucun dans le dépôt. On relance à la main en tuant d'abord l'instance en cours, ce qui suppose de savoir qu'il faut le faire. ## Versions - `@eqs/cli`, `@eqs/auth`, `@eqs/testing` : 0.8.1 - Prisma / `@prisma/client` : 6.19.3 - `pnpm@11.8.0`, Node v24.15.0, Windows 11 Pro 26200 - Gabarit : `apps/api/package.json`, script `dev` Trouvé en développant le premier satellite réel (Atelier, ex-Pilotage), en relançant l'API après un renommage.
Author
Owner

Vrai, et c'est votre option 1 — après avoir vérifié sa prémisse, qui tient.

Ce que j'ai vérifié

dev         : pnpm db:schema && prisma generate && concurrently …
postinstall : pnpm db:schema && prisma generate
build       : pnpm db:schema && prisma generate && tsc -p tsconfig.build.json
db:dev      : prisma migrate dev

Le client est donc produit à l'installation et au build. Et prisma migrate dev régénère de lui-même après une migration. Le generate de dev ne couvrait rien que les trois autres ne couvrent déjà — il ne restait que son coût.

Retiré. db:schema reste : la copie d'auth.prisma depuis @eqs/auth est idempotente, rapide, et c'est elle qui garde le schéma aligné sur le socle après une mise à jour. Votre remarque accessoire est juste, mais celle-là gagne son passage.

Votre point 2 est le vrai

C'est le cas NORMAL en développement, pas un cas limite.

C'est ce qui fait la différence entre un agacement et un défaut. Relancer après un changement de variable, après un git checkout, ou parce qu'un agent a redémarré la pile — et pendant ce temps l'API qui tourne encore laisse croire que tout va bien, jusqu'à ce qu'on cherche pourquoi le code modifié ne prend pas. Votre « j'ai cru avoir perdu le serveur alors qu'il tournait toujours » dit exactement le coût.

Vos options 2 et 3, écartées

prisma generate || true aurait gardé le geste en masquant son échec. C'est la forme qui rend un défaut invisible sans le supprimer : le jour où generate échoue pour une vraie raison — un schéma invalide — plus rien ne le dit.

Nommer la cause de l'EPERM aurait supposé d'envelopper une commande Prisma pour réinterpréter son message. Ça reste séduisant, et je ne l'ai pas fait pour une raison simple : une fois generate sorti de dev, l'EPERM ne survient plus que sur pnpm install ou pnpm build — deux commandes qu'on ne lance pas en boucle pendant qu'une API tourne. Le coût du message n'est plus payé au même rythme.

Si vous le rencontrez quand même sur install, dites-le : à ce moment-là l'enveloppe se justifie, et ce sera un ticket avec sa propre fréquence.

Ce qui est écrit

L'AGENTS.md du gabarit porte maintenant la raison, à côté de la commande — pour qu'on ne le remette pas dans six mois « par sécurité » :

dev ne rejoue pas prisma generate : le client est produit au postinstall et par db:dev, et le régénérer à chaque démarrage échouait en EPERM sous Windows dès qu'une autre instance tenait le moteur — avec un message qui ne nommait ni Prisma ni la cause.

État

Branche fix/prisma-generate-dev, tirée de main, fusionnable tout de suite : elle ne dépend d'aucune version du socle. C'est la seule des quatre branches du gabarit qui ne vous attend pas.

Vrai, et c'est votre option 1 — après avoir vérifié sa prémisse, qui tient. ## Ce que j'ai vérifié ``` dev : pnpm db:schema && prisma generate && concurrently … postinstall : pnpm db:schema && prisma generate build : pnpm db:schema && prisma generate && tsc -p tsconfig.build.json db:dev : prisma migrate dev ``` Le client est donc produit à l'installation **et** au build. Et `prisma migrate dev` régénère de lui-même après une migration. Le `generate` de `dev` ne couvrait rien que les trois autres ne couvrent déjà — il ne restait que son coût. Retiré. `db:schema` reste : la copie d'`auth.prisma` depuis `@eqs/auth` est idempotente, rapide, et c'est elle qui garde le schéma aligné sur le socle après une mise à jour. Votre remarque accessoire est juste, mais celle-là gagne son passage. ## Votre point 2 est le vrai > C'est le cas **NORMAL** en développement, pas un cas limite. C'est ce qui fait la différence entre un agacement et un défaut. Relancer après un changement de variable, après un `git checkout`, ou parce qu'un agent a redémarré la pile — et pendant ce temps l'API qui tourne encore laisse croire que tout va bien, jusqu'à ce qu'on cherche pourquoi le code modifié ne prend pas. Votre « j'ai cru avoir perdu le serveur alors qu'il tournait toujours » dit exactement le coût. ## Vos options 2 et 3, écartées **`prisma generate || true`** aurait gardé le geste en masquant son échec. C'est la forme qui rend un défaut invisible sans le supprimer : le jour où `generate` échoue pour une vraie raison — un schéma invalide — plus rien ne le dit. **Nommer la cause de l'EPERM** aurait supposé d'envelopper une commande Prisma pour réinterpréter son message. Ça reste séduisant, et je ne l'ai pas fait pour une raison simple : une fois `generate` sorti de `dev`, l'EPERM ne survient plus que sur `pnpm install` ou `pnpm build` — deux commandes qu'on ne lance pas en boucle pendant qu'une API tourne. Le coût du message n'est plus payé au même rythme. Si vous le rencontrez quand même sur `install`, dites-le : à ce moment-là l'enveloppe se justifie, et ce sera un ticket avec sa propre fréquence. ## Ce qui est écrit L'`AGENTS.md` du gabarit porte maintenant la raison, à côté de la commande — pour qu'on ne le remette pas dans six mois « par sécurité » : > `dev` ne rejoue **pas** `prisma generate` : le client est produit au `postinstall` et par `db:dev`, et le régénérer à chaque démarrage échouait en `EPERM` sous Windows dès qu'une autre instance tenait le moteur — avec un message qui ne nommait ni Prisma ni la cause. ## État Branche `fix/prisma-generate-dev`, tirée de `main`, **fusionnable tout de suite** : elle ne dépend d'aucune version du socle. C'est la seule des quatre branches du gabarit qui ne vous attend pas.
Author
Owner

Corrigé, et par votre voie 1 : prisma generate est sorti du script dev. Celui d'apps/api est désormais

pnpm db:schema && concurrently -k -n types,serveur -c blue,green "pnpm dev:types" "pnpm dev:serveur"

La génération reste où elle protégeait déjà quelque chose — postinstall et le script de migration. La régénérer à chaque dev ne couvrait rien de plus, et elle échouait en EPERM dès qu'une autre instance tenait le moteur.

db:schema reste en tête, comme vous le notiez : idempotent et rapide.

Fermé après vérification sur dev le 2026-09-21. Ce ticket restait ouvert faute d'un Closes #24 dans le commit, pas faute de correctif.

Corrigé, et par votre voie 1 : `prisma generate` est **sorti du script `dev`**. Celui d'`apps/api` est désormais ``` pnpm db:schema && concurrently -k -n types,serveur -c blue,green "pnpm dev:types" "pnpm dev:serveur" ``` La génération reste où elle protégeait déjà quelque chose — `postinstall` et le script de migration. La régénérer à chaque `dev` ne couvrait rien de plus, et elle échouait en `EPERM` dès qu'une autre instance tenait le moteur. `db:schema` reste en tête, comme vous le notiez : idempotent et rapide. Fermé après vérification sur `dev` le 2026-09-21. Ce ticket restait ouvert faute d'un `Closes #24` dans le commit, pas faute de correctif.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Infrastructure/eqs-satellite-template#24
No description provided.