Skip to content

fix(cli): exclude noise directories from --watch file watcher - #20388

Open
SomSamantray wants to merge 4 commits into
tailwindlabs:mainfrom
SomSamantray:fix/cli-watch-ignore-noise-dirs
Open

fix(cli): exclude noise directories from --watch file watcher#20388
SomSamantray wants to merge 4 commits into
tailwindlabs:mainfrom
SomSamantray:fix/cli-watch-ignore-noise-dirs

Conversation

@SomSamantray

Copy link
Copy Markdown

Summary

@tailwindcss/cli --watch can peg the CPU because its file watcher recurses into node_modules and .git with no filtering, causing spurious rebuild cycles. This adds a targeted ignore option to @parcel/watcher's subscribe() calls to exclude those directories, refined so it doesn't break an explicit @source path that legitimately lives inside node_modules.

Related: #17246

Test plan

Verified the ignore mechanism directly against the installed @parcel/watcher binary (spurious rebuilds eliminated; an explicit @source-into-node_modules case still triggers rebuilds correctly) and added e2e coverage in integrations/cli/index.test.ts.


Compound Engineering

SomSamantray added 3 commits August 5, 2026 20:46
Prevent the CLI's file watcher from recursing into node_modules, .git,
.hg, and .svn by passing an ignore option to @parcel/watcher's
subscribe(). Previously these directories were watched with no
filtering, causing spurious rebuild cycles and contributing to the
reported CPU-pegging hang on cold Watchman starts against large,
uncached trees.

Fixes tailwindlabs#17246
Adds coverage for the ignore-option fix: asserts that changes inside
node_modules or .git no longer trigger a watcher rebuild cycle, while
confirming the watcher still reacts to real source changes.

Could not be executed in this environment (requires a full monorepo
build producing dist/ tarballs, which needs the native Oxide crate);
the underlying mechanism was independently verified against the
installed @parcel/watcher@2.6.0 binary directly.
Two independent code-review passes (correctness and reliability) found
that a blanket node_modules/.git ignore on every watcher.subscribe()
call can silently break watch-mode rebuilds for an explicit @source
path living inside node_modules, if that path's own watch root gets
collapsed into a broader ancestor by the existing dedup step -- since
@parcel/watcher matches ignore globs relative to the watched root.

watchIgnoreFor() now skips ignoring a noise segment for a given
directory when another originally-requested directory got collapsed
into it and lives inside that segment. Verified empirically against
the installed @parcel/watcher@2.6.0 binary directly (0 events before
this fix for the nested case, 2 after -- matching pre-regression
behavior), and with isolated unit tests of the pure ignore-list logic.

Also fixes a test timeout that wasn't scaled for Windows like the rest
of the suite (per testing-reviewer), and adds e2e coverage for the
nested-@source-inside-node_modules scenario.
@SomSamantray
SomSamantray requested a review from a team as a code owner August 5, 2026 15:38
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The CLI watcher now ignores default noisy directory segments and computes ignore globs for each watched root. It preserves explicitly requested nested sources and tracked full-rebuild paths during watcher recreation. Integration tests verify that changes in node_modules and .git do not trigger rebuilds, while explicit nested sources and tracked CSS dependencies in node_modules do trigger rebuilds.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: excluding noise directories from the --watch file watcher to fix CPU overhead.
Description check ✅ Passed The description clearly relates to the changeset, explaining the problem (CPU overhead from recursive watching), the solution (ignore filters), and test coverage.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

The PR is not yet safe to merge because tracking an ordinary dependency inside node_modules disables that directory's ignore rule for the entire project watcher.

The dependency fix observes imported files again, but it does so by removing the project-root node_modules exclusion wholesale, allowing unrelated dependency-tree activity to trigger watcher processing and recreating the CPU and rebuild behavior the PR targets.

Files Needing Attention: packages/@tailwindcss-cli/src/commands/build/index.ts

Reviews (2): Last reviewed commit: "fix(cli): don't ignore noise dirs contai..." | Re-trigger Greptile

Comment thread packages/@tailwindcss-cli/src/commands/build/index.ts
Greptile review on tailwindlabs#20388 found that the ignore-list exemption only
considered scanner.normalizedSources (explicit @source paths), not
fullRebuildPaths -- compiler dependencies discovered via @import (e.g.
tailwindcss itself, or any imported CSS package resolved from
node_modules). If such a dependency's directory was nested under a
watched root with no other reason to be exempted, editing it would no
longer trigger a rebuild.

createWatchers() now also accepts the current fullRebuildPaths so
watchIgnoreFor() can exempt a noise segment for a root when a tracked
dependency lives inside it, the same way it already does for explicit
source directories. Verified with isolated unit tests of the ignore
logic covering both the reported regression and a control case
(unrelated dependency paths must not spuriously exempt anything), plus
new e2e coverage in integrations/cli/index.test.ts.
Comment on lines +895 to +897
let marker = `/${segment}/`
return !allRequestedDirs.some(
(other) => other !== dir && other.startsWith(`${dir}/`) && `${other}/`.includes(marker),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Dependency exemption restores noisy watches

When a normal Tailwind import or another compiler dependency resolves inside node_modules, watchIgnoreFor removes the node_modules ignore glob from the entire project-root subscription. Unrelated file events anywhere in that directory then reach the callback and initiate rebuild processing, restoring the spurious rebuilds and high CPU usage this change is intended to prevent.

Knowledge Base Used: Build tool integrations

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
integrations/cli/index.test.ts (1)

451-500: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise watcher recreation before the dependency update.

This test changes base.css while the initial watchers are active. It does not test the recreated watcher path in packages/@tailwindcss-cli/src/commands/build/index.ts at Line 401. A regression that drops fullRebuildPaths during watcher recreation will pass.

Modify src/index.css after startup, wait for the full rebuild, then modify base.css.

Proposed test addition
       await fs.expectFileToContain('dist/out.css', [
         css`
           .imported-marker {
             color: red;
           }
         `,
       ])
 
+      await fs.write(
+        'src/index.css',
+        css`
+          `@import` 'tailwindcss/utilities';
+          `@import` '../node_modules/my-base-styles/base.css';
+        `,
+      )
+      await process.onStderr((m) => m.includes('Done in'))
+
       await fs.write(
         'node_modules/my-base-styles/base.css',
         css`

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8abc1a82-0fb1-444e-9423-ecf27104b631

📥 Commits

Reviewing files that changed from the base of the PR and between 45921ed and 269b78a.

📒 Files selected for processing (2)
  • integrations/cli/index.test.ts
  • packages/@tailwindcss-cli/src/commands/build/index.ts

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant