Skip to content

fix(docs): stop the docs watcher polling 9.5k files every 100ms - #1937

Merged
misama-ct merged 1 commit into
mainfrom
fix-docs-watcher-polling-cpu
Aug 19, 2026
Merged

fix(docs): stop the docs watcher polling 9.5k files every 100ms#1937
misama-ct merged 1 commit into
mainfrom
fix-docs-watcher-polling-cpu

Conversation

@misama-ct

@misama-ct misama-ct commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

The dev watcher burned ~80% of a CPU core while completely idle. On a running dev server the process had consumed 79 minutes of CPU in 100 minutes
of wall clock
, doing nothing but watching files.

apps/docs/scripts/watcher.ts passed usePolling: true to chokidar. No
behavior change to generated output — this is purely a dev-loop cost fix.

Root cause

chokidar v4 honors usePolling by calling fs.watchFile on every watched
path, which re-stats all of them every interval (100 ms) forever. The
predicate matched 9,491 files under packages/, so an idle watcher was
issuing roughly 95,000 stat syscalls per second.

Two things made this pure waste:

  • The tree is local APFS (apfs, local, journaled) and emits native change
    events. Polling is only needed for filesystems that don't — network mounts,
    some container bind mounts.
  • The flag was present in the file's first commit (f92fb51), not added to
    work around a filesystem that needed it. It reads as an unexamined default.

Separately, 5,444 of those 9,491 files were in dist/ and 2,123 were
generated material-icons. Neither feeds the docs: routes come from .mdx
under src/, and types are parsed from packages/nimbus/src/index.ts. Watching
dist/ also meant every pnpm build fired thousands of change events that
each re-triggered a full type re-parse.

Changes

  • usePolling: false — stay on native fs.watch. Commented with why, and with
    the condition under which polling would legitimately be needed, so it doesn't
    get reinstated by reflex.
  • Ignore build output (/dist/).
  • Ignore generated icon components (material-icons).

Measured effect

Same tree, same ignore semantics otherwise:

before after
files watched 9,491 1,924
idle CPU 82.2% of one core 0.0%
startup CPU 1.01 s 0.35 s

Verification

  • A/B, identical config apart from the flag: 81.2% of a core idle → 0.0%.
  • Native watching still fires: confirmed change, add and unlink all
    detected on this case-sensitive APFS volume (working tree left clean).
  • Generated output unchanged — ran the patched watcher against the real
    packages/ tree in an isolated sandbox and diffed against the live baseline:
    • 178/178 route JSON files byte-identical
    • 367/367 type files present; the only content deltas are non-deterministic
      TypeScript internal symbol ids (__@iterator@1709564 vs @1070), which
      vary between program instances and are unrelated to watching
    • search-index.json byte-identical
    • route-manifest.json identical after a deep sort — same 178 routes, same
      categories, same navigation; only entry order differs
  • prettier --check and eslint clean on the changed file.

Regression test

None added. There's no existing harness for asserting watcher CPU or syscall
volume, and a wall-clock CPU assertion would be flaky in CI. The guard here is
the comment on the flag explaining when polling is and isn't appropriate. Happy
to add a test if reviewers want one.

Not in scope

  • route-manifest.json entry order follows filesystem scan order, so it can
    legitimately differ between runs. Pre-existing and unaffected by this change
    (content is identical), but worth knowing if anyone ever diffs that file.
  • apps/docs/tsconfig.node.json reports vite.config.ts(114,24): error TS18048: 'assetInfo.name' is possibly 'undefined'. Confirmed pre-existing on a clean
    main; left untouched.

`usePolling: true` made chokidar `fs.watchFile` every watched path, so it
re-`stat`ed all of them every 100ms forever — ~80% of a CPU core while the
dev server sat completely idle. The tree is local APFS and emits native
change events, so polling bought nothing here; the flag was present in the
script's first commit rather than added to work around a filesystem that
needed it.

Also stop watching build output and generated icon components. Neither
feeds the docs — routes come from `.mdx` under `src/`, types are parsed
from `packages/nimbus/src/index.ts` — and watching `dist/` meant every
build fired thousands of `change` events that each re-triggered a full
type re-parse.

Idle CPU 82% -> 0% of a core; watched files 9,491 -> 1,924; startup
1.01s -> 0.35s. Generated output is unchanged: 178 route JSON files and
367 type files byte-identical, search index identical, manifest identical
after deep sort (only scan-order differs).

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@misama-ct
misama-ct requested a review from a team as a code owner August 19, 2026 08:01
@changeset-bot

changeset-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 73c22d7

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nimbus-documentation Ready Ready Preview Aug 19, 2026 8:05am
nimbus-storybook Ready Ready Preview Aug 19, 2026 8:05am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Report

Last updated: 2026-08-19 08:03:42 UTC

Package Format Current Baseline Delta Status
@commercetools/nimbus dist 18950.9 KB 18950.9 KB -0.0% ✅ ok
@commercetools/nimbus-icons dist 4787.6 KB 4787.6 KB +0.0% ✅ ok
@commercetools/nimbus-tokens dist 408.9 KB 408.9 KB +0.0% ✅ ok

Baseline source: comment-chain

@misama-ct
misama-ct merged commit 86937a7 into main Aug 19, 2026
12 checks passed
@misama-ct
misama-ct deleted the fix-docs-watcher-polling-cpu branch August 19, 2026 12:54
@github-actions github-actions Bot added the bundle-sizes Housekeeping for merged PRs - allows the fetch sizes script to find the latest bundle check comment. label Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bundle-sizes Housekeeping for merged PRs - allows the fetch sizes script to find the latest bundle check comment.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants