
O deploy que quebrava sozinho — pnpm, audit fix e o ERR_PNPM_EEXIST
⚡ O deploy que quebrava sozinho
Existe um tipo de bug silencioso que é pior que crash: o que aparece sem ninguém mexer em nada. Era exatamente isso que tava acontecendo com o Portfólio em agosto de 2026 — o deploy na Vercel começou a recusar o build com um erro esotérico:
ERR_PNPM_EEXIST
Ninguém tinha tocado no código. O site local rodava lindo. Mas a Vercel dizia não. A investigação revelou uma história de duas ferramentas brigando entre si: o cron de segurança que atualiza dependências, e o pnpm que o package.json declarava.
🧠 Contexto — o guardião que virou vilão
O Portfólio vive em produção na Vercel (samuelmedeiros.vercel.app) e self-host na porta 3001. O deploy era manual: vercel --token "$VERCEL_TOKEN" --prod.
O elenco:
package.jsondeclaravapackageManager: "[email protected]"— era isso que a Vercel usava no builddependency-sast-scan.sh(cron de segurança, sexta 8h) rodapnpm audit fix— atualiza deps e regenera o lockfile- O
pnpmdo PATH do WSL… às vezes era o do Windows (/mnt/c/.../npm), versão 10.34.5
O audit fix rodou com o pnpm 10, gerou um lockfile v10. O packageManager continuava dizendo 9.12.3. Na Vercel: pnpm 9 tentando ler lock v10 + achatando node_modules de uma dep com bundled dependencies → ERR_PNPM_EEXIST.
🔧 A luta — três fixes até achar a raiz
Fix 1: alinhar o packageManager (commit 6e90b6b)
// package.json
- "packageManager": "[email protected]"
+ "packageManager": "[email protected]"
A Vercel passou a usar o pnpm 10 (a mesma versão do lockfile). Menos mal — mas o erro persistiu em outro ponto.
Fix 2: matar o node-linker=hoisted (commit 0aa26b2)
O .npmrc tinha node-linker=hoisted (herdado de config antiga). O hoisted faz o pnpm achatar o node_modules interno do @parcel/watcher-wasm (uma dep com bundledDependencies) — e o rename conflita com o cache restaurado do build:
O hoisted achata o node_modules interno do @parcel/watcher-wasm
(bundledDependencies) → rename conflita com o cache restaurado do build
→ ERR_PNPM_EEXIST. Na Vercel/Linux o isolated é o padrão e funciona.
# .npmrc — depois
store-dir=/tmp/.pnpm-store
Fix 3: alinhar o CI (commit d90d212)
O GitHub Actions ainda instalava pnpm 9.12.3 no pnpm/action-setup — desalinhado do packageManager. Subiu pra 10.34.5.
A causa raiz real
O problema de fundo não era o pnpm 9 nem o 10 — era o PATH. O cron de segurança rodava pnpm audit fix com o pnpm do Windows (o PATH incluía /mnt/c/.../npm), não o do WSL. Cada sexta, ele regenerava o lockfile com uma versão diferente da declarada — e o deploy da semana seguinte quebrava “sozinho”.
💡 Resolução — a regra do alinhamento
Depois dos três fixes, o princípio ficou claro e virou regra:
pnpm local == packageManager do package.json == pnpm do CI
E a regra prática pra segurança: checar a versão do PATH antes de rodar audit fix — o binário certo é /home/samuel/.hermes/node/bin/pnpm, não o do Windows.
O deploy voltou a passar. E o aprendizado mais valioso: a ferramenta feita pra proteger o projeto (scan de segurança) quase derrubou a produção — porque o ambiente onde ela rodava não era o mesmo do build.
📊 Métricas
| Métrica | Antes | Depois |
|---|---|---|
| packageManager | [email protected] | [email protected] |
| Lockfile | v10 (regenerado pelo Windows) | v10 (alinhado ao packageManager) |
| node-linker | hoisted (achatava bundled deps) | removido (isolated padrão) |
| CI pnpm/action-setup | 9.12.3 | 10.34.5 |
| Deploy Vercel | ERR_PNPM_EEXIST | ✅ estável |
| Commits | 529 | 530 |
🎯 Aprendizados
- “Funciona local” não significa “funciona na Vercel” — o ambiente de build é outro: outra versão de pnpm, outro node_modules, outro cache. Se o lockfile e o packageManager divergirem, o deploy quebra.
- Ferramenta de segurança pode derrubar produção — o
pnpm audit fix(que existe pra proteger) regenerou o lockfile com a versão errada. Automação de segurança precisa rodar no MESMO ambiente do build. - O PATH é uma armadilha no WSL —
/mnt/c/.../npmno PATH significa que o binário errado pode ser escolhido silenciosamente. Sempre pinar o caminho absoluto do binário certo. - O erro esotérico tem causa raiz simples — ERR_PNPM_EEXIST parecia mágica, mas era só versão divergente de pnpm + linker errado. Diagnóstico em camadas: packageManager → .npmrc → CI → PATH.