You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When @tailwindcss/upgrade is run from a subpackage of a pnpm workspace, it traverses ../../node_modules/… and rewrites files inside the installed tailwindcss package in place — applying its v3 → v4 migration transform (@tailwind utilities; → @import 'tailwindcss/utilities' layer(utilities);) to node_modules/tailwindcss/utilities.css (and, in a flat-project variant, to index.css).
This looks like the tail of #19726 — the fix landed there (ignore gitignored files) works for the simple flat-project case, but doesn't cover the workspace-subpackage variant where the tool reaches node_modules through ../../. In our case (a pnpm-workspace v4 project), running the tool as a canonicalizer left node_modules/tailwindcss/utilities.css self-referentially importing itself, which detonated much later as an infinite CSS-resolution loop and read like a "cache corruption" problem.
node_modules is gitignored at the repo root (and even at the subpackage level, tested separately — same result).
Counter-tests (where the existing fix works correctly)
For contrast, both of these leave node_modules untouched:
Flat project (no workspace), node_modules in .gitignore, @import 'tailwindcss', run from project root
Same, using explicit-path imports (@import 'tailwindcss/utilities.css' layer(utilities) source(none))
The pnpm-workspace-subpackage layout is the specific gap.
Why this matters
node_modules shouldn't be a write target. The files are shared across projects via the pnpm store, are immutable install artifacts elsewhere, and are out of scope for git status, so the corruption is invisible until it detonates.
The rewrite creates a self-referential import — the mutated utilities.css imports itself under a layer(...) wrapper, causing an infinite CSS-resolution loop.
Symptoms look unrelated to the upgrade run — the user hits infinite-recursion errors much later, from any command that touches Tailwind, with a clean git status.
Skip any file whose resolved absolute path falls under a node_modules/ segment, regardless of how it was reached (traversal via ../../, symlink resolution, or @import resolution). The existing gitignore-aware filter probably just needs its input paths canonicalized before the check.
Summary
When
@tailwindcss/upgradeis run from a subpackage of a pnpm workspace, it traverses../../node_modules/…and rewrites files inside the installedtailwindcsspackage in place — applying its v3 → v4 migration transform (@tailwind utilities;→@import 'tailwindcss/utilities' layer(utilities);) tonode_modules/tailwindcss/utilities.css(and, in a flat-project variant, toindex.css).This looks like the tail of #19726 — the fix landed there (ignore gitignored files) works for the simple flat-project case, but doesn't cover the workspace-subpackage variant where the tool reaches
node_modulesthrough../../. In our case (a pnpm-workspace v4 project), running the tool as a canonicalizer leftnode_modules/tailwindcss/utilities.cssself-referentially importing itself, which detonated much later as an infinite CSS-resolution loop and read like a "cache corruption" problem.Reproduction
Repo: https://github.com/benface/tailwindcss-upgrade-node-modules-repro
The tool's own output confirms the write:
Diff against a pristine copy from the tarball:
node_modulesis gitignored at the repo root (and even at the subpackage level, tested separately — same result).Counter-tests (where the existing fix works correctly)
For contrast, both of these leave
node_modulesuntouched:node_modulesin.gitignore,@import 'tailwindcss', run from project root@import 'tailwindcss/utilities.css' layer(utilities) source(none))The pnpm-workspace-subpackage layout is the specific gap.
Why this matters
node_modulesshouldn't be a write target. The files are shared across projects via the pnpm store, are immutable install artifacts elsewhere, and are out of scope forgit status, so the corruption is invisible until it detonates.utilities.cssimports itself under alayer(...)wrapper, causing an infinite CSS-resolution loop.git status.node_modules+ reinstalling (and on Bun, purging the install cache too, per Exceeded maximum recursion depth while resolvingtailwindcss/utilities#19726). Not obvious from the symptoms.Suggested guard
Skip any file whose resolved absolute path falls under a
node_modules/segment, regardless of how it was reached (traversal via../../, symlink resolution, or@importresolution). The existing gitignore-aware filter probably just needs its input paths canonicalized before the check.Environment
tailwindcss@4.3.2@tailwindcss/upgrade@4.3.2(viapnpm dlx)Happy to test a fix.