Skip to content

CLI Watch never end 2 CPU cores at 100% #17246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
deguich opened this issue Mar 17, 2025 · 26 comments
Open

CLI Watch never end 2 CPU cores at 100% #17246

deguich opened this issue Mar 17, 2025 · 26 comments

Comments

@deguich
Copy link

deguich commented Mar 17, 2025

What version of Tailwind CSS are you using?

v4.0.14

What version of Node.js are you using?

v20.18.0

What operating system are you using?

Windows 11

Reproduction URL

I just use the Tailwind4 directory of https://github.com/pinzonjulian/tailwind_css_cli_watch_bug.git

git clone https://github.com/pinzonjulian/tailwind_css_cli_watch_bug.git
cd tailwind4
npm install tailwindcss @tailwindcss/cli
npx @tailwindcss/cli -i input.css -o output.css --watch

Describe your issue

Command never end, there is no output, 2 cores at 100%.

If I remove output.css, the process don't create it.

When I remove --watch.
It works :

> $env:DEBUG=1; npx @tailwindcss/cli -i input.css -o output.css
≈ tailwindcss v4.0.14

Done in 91ms

[95.04ms] [@tailwindcss/cli] (initial build)
[14.09ms]   ↳ Setup compiler
[38.04ms]   ↳ Scan for candidates
[33.33ms]   ↳ Build CSS
[ 1.06ms]   ↳ Write output

Could a global configuration be the cause of the problem?

@deguich
Copy link
Author

deguich commented Mar 17, 2025

I encounter the same problem with:

  • tailwindcss.exe -i input.css -o output.css --watch
  • $env:NODE_PATH=""; $env:DEBUG=1; npx @tailwindcss/cli -i input.css -o output.css -w
  • $env:NODE_PATH=""; $env:DEBUG=1; npx @tailwindcss/cli -i input.css -o output.css -w after renaming ~/AppData/Roaming npm and npm-cache (same with Appdata/Local...)

@philipp-spiess
Copy link
Member

Hey! I tried running your example on macOS and it works fine (I tried with your exact Node version and v22 (what I had installed before):

Image

Going to try this out on Windows 11 later too, but I wonder what happens if you run the --watch command with the DEBUG=1 flag?

Command never end

This is expected for the --watch command as it needs to stay alive to detect file changes

@deguich
Copy link
Author

deguich commented Mar 17, 2025

With DEBUG=1, there is no output and 2 cores at 100%.

@deguich
Copy link
Author

deguich commented Mar 17, 2025

I got the same result with Node.js v22.14.0 (npm 10.9.2).

@deguich
Copy link
Author

deguich commented Mar 17, 2025

I tried on another windows (v10). It works

@deguich
Copy link
Author

deguich commented Mar 17, 2025

Tailwind CSS seems to be incompatible with the Watchman (Meta) software.
Can you reproduce the issue by installing it via Chocolatey?

choco install watchman

If I delete the shim-generated file:
C:\ProgramData\chocolatey\bin\watchman.exe,
Tailwind CSS starts working again.

This suggests that Tailwind CSS is likely using Watchman in the background for file watching. When it launches the 2025 version found in the PATH (the one installed via Chocolatey), it may trigger unexpected behavior (infinite loop?).

@deguich
Copy link
Author

deguich commented Mar 18, 2025

I see on @parcel/watcher:

@parcel/watcher has the following watcher backends, listed in priority order:

[FSEvents](https://developer.apple.com/documentation/coreservices/file_system_events) on macOS
[Watchman](https://facebook.github.io/watchman/) if installed
[inotify](http://man7.org/linux/man-pages/man7/inotify.7.html) on Linux
[ReadDirectoryChangesW](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465%28v%3Dvs.85%29.aspx) on Windows
[kqueue](https://man.freebsd.org/cgi/man.cgi?kqueue) on FreeBSD, or as an alternative to FSEvents on macOS

A log about the watcher used or a compatibility check on the version could be a plus to facilitate problem identification.
If Tailwind CSS does not work regardless of the Watchman version, prohibiting the use of Watchman at the code level would then be a solution.

@philipp-spiess
Copy link
Member

@deguich Hey! Thanks for digging into it. Are you able to reproduce the problem when using @parcel/watcher directly? If so, it would be awesome if we can create an upstream bug report for that project and get it fixed for everyone using @parcel/watcher!

@lenart
Copy link

lenart commented Mar 25, 2025

I'm experiencing same problem on Mac M2 using Node v22.14.0 and Yarn 1.22.22. When running npx @tailwindcss/cli -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css the CPU (dockerized) goes to >100% and stays there. The Orbstack Helper (on the host OS) reports over a thousand % CPU usage 😓 .

I get the same issue when running on host OS using Node v23.7.0 (different than dockerized), yarn 1.22.22.

@lenart
Copy link

lenart commented Mar 25, 2025

I can confirm that locking version at 4.0.15 solves the issue for me. Uninstall any existing tailwind packages (locally and globally) and clear the cache npm cache clean --force. Then install 4.0.15:

npm install @tailwindcss/cli@4.0.15
npx @tailwindcss/cli -i app.scss -o app.css # builds

@philipp-spiess
Copy link
Member

@lenart The issue you're running into might be #17379 and is unrelated to watch mode. Do you mind taking a look there and, if possible, can provide us with a reproduction of that issue? Thanks!

@flavorjones
Copy link

flavorjones commented Mar 26, 2025

I can reproduce what seems to be this issue when I have watchman installed; and it goes away when I uninstall watchman.

With watchman installed, on a Linux x86_64 GNU libc system (native, not in a container) running with DEBUG=1, I see:

22:14:29 css.1  | [81399.36ms] [@tailwindcss/cli] (initial build)
22:14:29 css.1  | [   12.32ms]   ↳ Setup compiler
22:14:29 css.1  | [   33.67ms]   ↳ Scan for candidates
22:14:29 css.1  | [   11.92ms]   ↳ Build CSS
22:14:29 css.1  | [    1.04ms]   ↳ Optimize CSS
22:14:29 css.1  | [    1.03ms]   ↳ Write output

The initial startup takes 81 seconds. After that startup, everything seems responsive and changes to files result in:

22:15:42 css.1  | Done in 1ms
22:15:42 css.1  |
22:15:42 css.1  | [1.34ms] [@tailwindcss/cli] (watcher)
22:15:42 css.1  | [1.13ms]   ↳ Scan for candidates

Without watchman installed, though, startup is 1000x faster:

22:16:59 css.1  | Done in 71ms
22:16:59 css.1  |
22:16:59 css.1  | [71.90ms] [@tailwindcss/cli] (initial build)
22:16:59 css.1  | [12.04ms]   ↳ Setup compiler
22:16:59 css.1  | [26.72ms]   ↳ Scan for candidates
22:16:59 css.1  | [15.86ms]   ↳ Build CSS
22:16:59 css.1  | [ 1.23ms]   ↳ Optimize CSS
22:16:59 css.1  | [ 0.81ms]   ↳ Write output

@ChadMoran
Copy link

This was happening to me on Node v20.16.0 on macOS 15.3.2 with ARM. Upgrading to Node v22.14.0 resolved it.

@silva96
Copy link

silva96 commented Apr 1, 2025

This is happening to me in 22.14.0 too. 197% CPU usage always. I use mac m1

@philipp-spiess
Copy link
Member

@flavorjones What specific versions of watchman and Tailwind CLI did you use to reproduce this? I've been playing around on a Debian based x64 linux (no containers) now too and with both these versions (one installed via brew and one via apt), the repro from @deguich worked fine:

Image Image

I tried it with both the latest standalone build and with the latest CLI and Node.js

@silva96/ @ChadMoran Was your issue also watchman based? Do you have a repro that you can share?

@philipp-spiess
Copy link
Member

@deguich Can also not reproduce the issue on Windows 11 Node 20 I'm afraid:

Image

Maybe this requires a very specific version of watchman to trigger?

@sumansuhag

This comment has been minimized.

@flavorjones
Copy link

flavorjones commented May 13, 2025

@philipp-spiess Thanks for looking into this. I reproduce this consistently on:

  • Ubuntu 24.10 ("oracular"), native x86_64
  • watchman 4.9.0-7build4 (installed via apt)
    • which installs libpcre3 2:8.39-15.1 as a dependency
  • tailwindcss CLI v4.1.6 (precompiled binary)

Here's a full console log that might be helpful to see what's going on and potentially reproduce. Note that the tailwindcss CLI is installed via the tailwindcss-ruby gem, but it's simply the precompiled executable shipped by the tailwindcss project -- the full command is included below.

$ git clone https://github.com/rails/tailwindcss-rails
...

$ cd tailwindcss-rails && bundle install
...

$ ./test/integration/user_install_test.sh
...

$ cd "My Workspace/test-install"

$ DEBUG=1 bin/rails tailwindcss:watch[verbose]
Running: /home/flavorjones/.local/share/mise/installs/ruby/3.4.2/lib/ruby/gems/3.4.0/gems/tailwindcss-ruby-4.1.6-x86_64-linux-gnu/exe/x86_64-linux-gnu/tailwindcss -i /home/flavorjones/code/oss/tailwindcss-rails/My\ Workspace/test-install/app/assets/tailwind/application.css -o /home/flavorjones/code/oss/tailwindcss-rails/My\ Workspace/test-install/app/assets/builds/tailwind.css --minify -w
≈ tailwindcss v4.1.6

sh: 1: watchman: not found
Done in 74ms

[74.78ms] [@tailwindcss/cli] (initial build)
[15.17ms]   ↳ Setup compiler
[15.64ms]   ↳ Scan for candidates
[20.87ms]   ↳ Build CSS
[ 2.26ms]   ↳ Optimize CSS
[ 0.76ms]   ↳ Write output

^C

$ sudo apt install watchman
Installing:
  watchman

Installing dependencies:
  libpcre3

Suggested packages:
  python3-pywatchman

Summary:
  Upgrading: 0, Installing: 2, Removing: 0, Not Upgrading: 1
  Download size: 576 kB
  Space needed: 1,559 kB / 1,399 GB available

Continue? [Y/n] y
Get:1 http://archive.ubuntu.com/ubuntu oracular/universe amd64 libpcre3 amd64 2:8.39-15.1 [253 kB]
Get:2 http://archive.ubuntu.com/ubuntu oracular/universe amd64 watchman amd64 4.9.0-7build4 [323 kB]
Fetched 576 kB in 0s (1,404 kB/s)
Selecting previously unselected package libpcre3:amd64.
(Reading database ... 335218 files and directories currently installed.)
Preparing to unpack .../libpcre3_2%3a8.39-15.1_amd64.deb ...
Unpacking libpcre3:amd64 (2:8.39-15.1) ...
Selecting previously unselected package watchman.
Preparing to unpack .../watchman_4.9.0-7build4_amd64.deb ...
Unpacking watchman (4.9.0-7build4) ...
Setting up libpcre3:amd64 (2:8.39-15.1) ...
Setting up watchman (4.9.0-7build4) ...
Processing triggers for man-db (2.12.1-3) ...
Processing triggers for libc-bin (2.40-1ubuntu3) ...

$ DEBUG=1 bin/rails tailwindcss:watch[verbose]
Running: /home/flavorjones/.local/share/mise/installs/ruby/3.4.2/lib/ruby/gems/3.4.0/gems/tailwindcss-ruby-4.1.6-x86_64-linux-gnu/exe/x86_64-linux-gnu/tailwindcss -i /home/flavorjones/code/oss/tailwindcss-rails/My\ Workspace/test-install/app/assets/tailwind/application.css -o /home/flavorjones/code/oss/tailwindcss-rails/My\ Workspace/test-install/app/assets/builds/tailwind.css --minify -w
≈ tailwindcss v4.1.6

Done in 1m

[81330.90ms] [@tailwindcss/cli] (initial build)
[   15.92ms]   ↳ Setup compiler
[   12.08ms]   ↳ Scan for candidates
[   23.07ms]   ↳ Build CSS
[    6.28ms]   ↳ Optimize CSS
[    2.28ms]   ↳ Write output

^C

@philipp-spiess
Copy link
Member

@flavorjones Hmm this is getting bizarre. I just provisioned a new VPS with your repro and still can't reproduce this:

root@ubuntu-4gb-hel1-2:~/tailwindcss-rails/My Workspace/test-install# lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 24.04.2 LTS
Release:	24.04
Codename:	noble
root@ubuntu-4gb-hel1-2:~/tailwindcss-rails/My Workspace/test-install# ruby -v
ruby 3.2.3 (2024-01-18 revision 52bb2ac0a6) [x86_64-linux-gnu]
root@ubuntu-4gb-hel1-2:~/tailwindcss-rails/My Workspace/test-install# watchman -v
4.9.0
root@ubuntu-4gb-hel1-2:~/tailwindcss-rails/My Workspace/test-install# DEBUG=1 bin/rails tailwindcss:watch[verbose]
Running: /var/lib/gems/3.2.0/gems/tailwindcss-ruby-4.1.6-x86_64-linux-gnu/exe/x86_64-linux-gnu/tailwindcss -i /root/tailwindcss-rails/My\ Workspace/test-install/app/assets/tailwind/application.css -o /root/tailwindcss-rails/My\ Workspace/test-install/app/assets/builds/tailwind.css --minify -w
≈ tailwindcss v4.1.6

Done in 164ms

[165.34ms] [@tailwindcss/cli] (initial build)
[ 53.65ms]   ↳ Setup compiler
[ 15.24ms]   ↳ Scan for candidates
[ 41.02ms]   ↳ Build CSS
[  4.13ms]   ↳ Optimize CSS
[  1.68ms]   ↳ Write output

I wonder if this happens with plain @parcel/watcher for you, too! (I would love to try it with plain watchman but I don't know if you can do that with the CLI only). Here's what I did:

apt install nodejs npm
mkdir test
npm init
npm add @parcel/watcher
# paste in the content below into index.js
node index.js

# in different session
touch index.js
const watcher = require("@parcel/watcher");

watcher.subscribe(".", async (err, events) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(events);
}).then(console.log);

For me this immediately logs the unsubscribe function and also logs updates immediatly:

root@ubuntu-4gb-hel1-2:~/test# node index.js
{ unsubscribe: [Function: unsubscribe] }
[ { path: '/root/test/index.js', type: 'update' } ]
^C

@philipp-spiess
Copy link
Member

Looking at the implementation a bit, I wonder if it works if you set WATCHMAN_SOCK to /dev/null or something?

https://github.com/parcel-bundler/watcher/blob/master/src/watchman/WatchmanBackend.cc#L44C22-L58

WATCHMAN_SOCK=/dev/null node index.js

@flavorjones
Copy link

@philipp-spiess A thing I noticed is that watchman, when run, spins up a daemon that runs in the background for subsequent runs (I think? I'm not very familiar with the tool).

This issue -- the long initial build step -- only seems to happen when the daemon is not already running. If the daemon is running already, it's fine:

$ pgrep -a watchman
# ... no output ...

$ DEBUG=1 bin/rails tailwindcss:watch[verbose]
Running: /home/flavorjones/.local/share/mise/installs/ruby/3.4.2/lib/ruby/gems/3.4.0/gems/tailwindcss-ruby-4.1.6-x86_64-linux-gnu/exe/x86_64-linux-gnu/tailwindcss -i /home/flavorjones/code/oss/tailwindcss-rails/My\ Workspace/test-install/app/assets/tailwind/application.css -o /home/flavorjones/code/oss/tailwindcss-rails/My\ Workspace/test-install/app/assets/builds/tailwind.css --minify -w
≈ tailwindcss v4.1.6

Done in 1m

[81424.61ms] [@tailwindcss/cli] (initial build)
[   14.71ms]   ↳ Setup compiler
[   10.04ms]   ↳ Scan for candidates
[   16.06ms]   ↳ Build CSS
[    2.30ms]   ↳ Optimize CSS
[    1.15ms]   ↳ Write output

^C

$ pgrep -a watchman
3030265 watchman --output-encoding=bser get-sockname

$ DEBUG=1 bin/rails tailwindcss:watch[verbose]
Running: /home/flavorjones/.local/share/mise/installs/ruby/3.4.2/lib/ruby/gems/3.4.0/gems/tailwindcss-ruby-4.1.6-x86_64-linux-gnu/exe/x86_64-linux-gnu/tailwindcss -i /home/flavorjones/code/oss/tailwindcss-rails/My\ Workspace/test-install/app/assets/tailwind/application.css -o /home/flavorjones/code/oss/tailwindcss-rails/My\ Workspace/test-install/app/assets/builds/tailwind.css --minify -w
≈ tailwindcss v4.1.6

Done in 57ms

[57.74ms] [@tailwindcss/cli] (initial build)
[14.95ms]   ↳ Setup compiler
[ 8.54ms]   ↳ Scan for candidates
[12.88ms]   ↳ Build CSS
[ 2.16ms]   ↳ Optimize CSS
[ 0.69ms]   ↳ Write output

^C

That said, if the daemon is not running and I set WATCHMAN_SOCK=/dev/null then yes, it runs quickly:

$ pgrep -a watchman
# ... no output ...

$ WATCHMAN_SOCK=/dev/null DEBUG=1 bin/rails tailwindcss:watch[verbose]
Running: /home/flavorjones/.local/share/mise/installs/ruby/3.4.2/lib/ruby/gems/3.4.0/gems/tailwindcss-ruby-4.1.6-x86_64-linux-gnu/exe/x86_64-linux-gnu/tailwindcss -i /home/flavorjones/code/oss/tailwindcss-rails/My\ Workspace/test-install/app/assets/tailwind/application.css -o /home/flavorjones/code/oss/tailwindcss-rails/My\ Workspace/test-install/app/assets/builds/tailwind.css --minify -w
≈ tailwindcss v4.1.6

Done in 55ms

[55.56ms] [@tailwindcss/cli] (initial build)
[14.70ms]   ↳ Setup compiler
[ 7.36ms]   ↳ Scan for candidates
[15.88ms]   ↳ Build CSS
[ 2.19ms]   ↳ Optimize CSS
[ 0.91ms]   ↳ Write output

^C

@philipp-spiess
Copy link
Member

Yeah so it seems that for me, too, watchman is slower when it boots up the first time. However that's 1 second and not 80s 😬

Image

I've been playing a round a bit to figure out how the watchman CLI works in order to be able to test this independelty from tailwind and @parcel/watcher. Can you, in the same folder as above and with two terminal windows, try this:

watchman shutdown-server
watchman watch-del-all
watchman --persistent log-level debug

And in the other terminal

watchman watch .

I wonder specifically if these startups log and that initial crawl are stuck for you there too!

Thanks for your patience on this by the way, really appreciated!

@flavorjones
Copy link

@philipp-spiess I think I understand what's going on here.

First: I did as you asked, and ran these commands in two different terminal sessions in the Rails project directory. Nothing seems to be hanging for me. The logs seem to show immediate responsiveness, and when I touched a local file ("README.md") saw it immediately show up in the logs.

Here's the output from the debug command, in case it's useful: watch.log

Same result if I run the tailwindcss watch command after the log-level debug command.

Then I shut down watchman, ran the tailwindcss watch command again, and then turned on logging. I saw some interesting entries in the logs related to "bootsnap" files like:

tmp/cache/bootsnap/compile-cache-iseq/65/f7dcfb114e9906

Bootsnap is a library used by default by Rails to pre-parse and pre-compile Ruby code to speed up process start time. For this demo app, it generates 1600+ files spread across a nested directory hierarchy:

tmp/cache/bootsnap/compile-cache-iseq
├── 00
│   ├── 140445af65fb1a
│   ├── 20f662291b6eea
│   ├── 5ebb88599d2acd
│   ├── 6f59cfde8ca882
│   ├── b0f4bba1a2ba16
│   └── f4f975c4ab732f
├── 01
│   ├── 42c131cc81a33c
│   ├── 58142a3f4bcacb
│   ├── 6592f88488d32e
│   ├── 8ea536f537ac5b
│   └── e1d4ee43d1e7ad
├── 02
│   ├── 4fe871faf2aeea
│   └── d4ee747b08c0ae
├── 03
│   ├── 20d3fdc40a28bc
│   ├── 503b549529cbe8
│   ├── 56be1978cb5c95
│   ├── 8bf6383989658f
│   ├── a7c17f2f116ed0
│   ├── c7b09be8be7401
│   ├── de386bc5005c5c
│   └── eb3c372a4cbdcb
├── 04
│   ├── 0320d78aaffdd4
│   ├── 7d0454d70096c7
│   ├── 8ec7c67663ffd2
│   └── bfd92164cd134f

... ✀ ...

├── fe
│   ├── 35d6b178df2c44
│   ├── 5f4418d96d5fa3
│   └── b922f959e9f8c8
└── ff
    ├── 4070c3691c82ba
    ├── 5a321ff348d0ad
    ├── 650592d6de9c74
    └── a586489a434f99

256 directories, 1355 files

Playing around with it, I can turn off bootsnap but so long as those files are present, the tailwindcss watch command startup is slow.

I think this behavior is related to this issue: #15750. Because the watch command should not be looking at files in the tmp/ directory in the first place. WDYT?

@philipp-spiess
Copy link
Member

@flavorjones Ah yeah that is very helpful. It does seem like a blanked listen for all changes in this directory recursively won't cut it, which is annoying because @parcel/watcher only allows listening to directories and it's always recursive.

One option I am looking into is the ignore list now. We may be able to compute something for that in Oxide but before we go ahead and try that, I'd love to understand if this fixes your perf issues. Can I ask you for one more favor here? Try the following parcel script and let me know if the inclusion of that ignore option makes any difference:

const watcher = require("@parcel/watcher");

watcher
  .subscribe(
    ".",
    async (err, events) => {
      if (err) {
        console.error(err);
        return;
      }
      console.log(events);
    },
    { ignore: "./tmp" }
  )
  .then(console.log);

@philipp-spiess
Copy link
Member

I tried starting the rails app in my repro Linux VM but even though my tmp dir looks the same, it doesn't take that long to start. That said I'm on a Hetzner VPS where they do claim that they use NVMe drives so that might explain why it's fast for me. 🤔

So I naturally duplicated the tmp/ folder a dozen times and now it's getting slower (4sec already!). This does indeed seem like the issue is related to #15750.

Image

Now let me try if that ignore setup would work.

@flavorjones
Copy link

Try the following parcel script and let me know if the inclusion of that ignore option makes any difference

It doesn't seem to make a difference for me. It takes about 1m32s with or without the ignore parameter.

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

No branches or pull requests

7 participants