Skip to content

fix(jest-haste-map): tolerate a locked file while indexing on Windows - #16358

Merged
SimenB merged 4 commits into
jestjs:mainfrom
SimenB:haste-map-docs
Aug 17, 2026
Merged

fix(jest-haste-map): tolerate a locked file while indexing on Windows#16358
SimenB merged 4 commits into
jestjs:mainfrom
SimenB:haste-map-docs

Conversation

@SimenB

@SimenB SimenB commented Aug 17, 2026

Copy link
Copy Markdown
Member

Summary

One behavioural fix plus some minor documentation tweaks.

A file another process holds open aborts the crawl on Windows. workerError treated exactly two codes as recoverable:

if (!["ENOENT", "EACCES"].includes(error.code)) {
  throw error;
}

Anything else fails the whole build. On Windows that includes EPERM, which is what git maintenance touching .git/index.lock or .git/objects looks like from the outside — a transient condition that says nothing about the file being indexable. The watchers already survive it: #16295 added isIgnorableFileError for precisely this, but only on the watch path, so a crawl hitting the same file still died.

That helper lived in watchers/common.ts. Both the watch path and the crawl path want it now, so it moves to lib/isIgnorableFileError.ts instead of having lib/FileProcessor.ts reach sideways into watchers/ for an errno predicate. ParcelWatcher and WatchmanWatcher import it from there, and its tests move with it.

EACCES stays a separate condition rather than folding into the helper, because the two callers want different answers: an unreadable file should not tear a watcher down, but during indexing it only means this one file cannot be read. Collapsing them would silently drop EACCES tolerance.

Docs. Three corrections to packages/jest-haste-map/CLAUDE.md, and a comment in walk.ts:

  • ChangeQueue's 30 ms interval was described as a debounce. It is a periodic flush that emits whatever has queued and does not reset on activity.
  • The same line merged two distinct guards into one claim. They are separate: a Set of type:path:mtime keys drops duplicates within a frame, and a direct mtime comparison drops change events for files that were only accessed.
  • Added that ModuleMap's toJSON/fromJSON run for every test worker, not just for the on-disk cache — worth knowing before touching them, as fix(jest-haste-map): restore the nested duplicates index in ModuleMap.fromJSON #16353 showed.
  • Added that overlapping roots are collapsed by watchman via watch-project but walked separately by find(), so the shared subtree is traversed twice. The map stays correct, and statCache spares some of the repeated lstat calls — best-effort only, since the walks run concurrently and can both miss a path before either caches it. The traversal is always duplicated.
  • walk.ts: includeDirs and the dir entry kind have no caller and read as dead surface. They are there for symlinked-directory support.

Test plan

lib/__tests__/FileProcessor.test.ts asserts the changed line on both platforms: EPERM is tolerated on win32 and still thrown on linux. It loads FileProcessor through jest.isolateModules with node:os mocked, because the predicate reads the platform once at module load.

That test is the guard. The error-path cases around it — EACCES and ENOENT dropping the file without throwing, an unrecognised code (EISDIR) propagating — behave the same before and after this change, so on their own they left a full revert green. Reverting workerError to the original array check now fails:

● FileProcessor › processFile › on win32, an EPERM worker error keeps the file: false

FULL REVERT:   1 failed, 16 passed
as committed: 17 passed

watchers/__tests__/common.test.tslib/__tests__/isIgnorableFileError.test.ts as a rename, keeping its platform matrix (ENOENT everywhere, EPERM only on win32, EACCES never).

yarn jest packages/jest-haste-map

Test Suites: 23 passed, 23 total
Tests:       213 passed, 213 total
Snapshots:   5 passed, 5 total

Also green: yarn lint, yarn typecheck:tests, yarn check-changelog, yarn check-copyright-headers.

SimenB and others added 2 commits August 17, 2026 08:17
The agent notes called ChangeQueue's 30 ms interval a debounce; it is a
periodic flush that emits whatever has queued, and it does not reset on
activity. The same line merged two distinct guards — the event-key `Set` and
the mtime comparison — into one claim.

Add the two things a reader has to discover the hard way: that `ModuleMap`'s
`toJSON`/`fromJSON` run for every test worker rather than only for the cache,
and that overlapping roots are collapsed by watchman but traversed twice by
the node crawler.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`includeDirs` and `WalkEntryKind`'s `dir` case have no caller — `find()` asks
for files only — which reads as dead surface. Both exist for symlinked-
directory support, where following a link means walking the directory behind it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@netlify

netlify Bot commented Aug 17, 2026

Copy link
Copy Markdown

Deploy Preview for jestjs ready!

Name Link
🔨 Latest commit a618f0f
🔍 Latest deploy log https://app.netlify.com/projects/jestjs/deploys/6a82b3fb5bde7a0008592902
😎 Deploy Preview https://deploy-preview-16358--jestjs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

`workerError` treated only `ENOENT` and `EACCES` as recoverable, so an `EPERM`
from a file another process holds open failed the whole crawl. The watchers
already survive that case via `isIgnorableFileError` (jestjs#16295); file processing
did not.

Move that helper from `watchers/common.ts` into `lib/`: both the watch path and
the crawl path use it now, and `lib/FileProcessor.ts` should not reach sideways
into `watchers/` for an errno predicate. `EACCES` stays a separate condition: an
unreadable file should not tear a watcher down, but here it only means this one
file cannot be indexed.

Also correct the agent notes and add two comments from the same review pass:
`ChangeQueue`'s 30 ms interval is a periodic flush rather than a debounce, its
two dedup guards are distinct, `ModuleMap`'s `toJSON`/`fromJSON` run for every
test worker, overlapping roots are collapsed by watchman but walked twice by
`find()`, and `walk()`'s `includeDirs`/`dir` surface exists for symlinked
directories.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SimenB
SimenB requested a balanced review from Copilot August 17, 2026 06:37
@github-actions github-actions Bot added the require-changelog If a PR does requires a changelog entry label Aug 17, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Improves Windows crawl resilience by treating transient EPERM errors as ignorable during indexing.

Changes:

  • Shares the ignorable-file-error predicate across crawlers and watchers.
  • Adds worker error-path tests.
  • Updates haste-map documentation and changelog.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
CHANGELOG.md Records the Windows indexing fix.
packages/jest-haste-map/CLAUDE.md Updates architecture notes.
packages/jest-haste-map/src/lib/FileProcessor.ts Ignores Windows EPERM during indexing.
packages/jest-haste-map/src/lib/__tests__/FileProcessor.test.ts Expands worker-error tests.
packages/jest-haste-map/src/lib/__tests__/isIgnorableFileError.test.ts Relocates helper tests.
packages/jest-haste-map/src/lib/isIgnorableFileError.ts Houses the shared error predicate.
packages/jest-haste-map/src/lib/walk.ts Documents directory-entry support.
packages/jest-haste-map/src/watchers/ParcelWatcher.ts Imports the relocated helper.
packages/jest-haste-map/src/watchers/WatchmanWatcher.js Imports the relocated helper.
packages/jest-haste-map/src/watchers/common.ts Removes the former helper definition.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/jest-haste-map/src/lib/FileProcessor.ts
Comment thread packages/jest-haste-map/src/lib/isIgnorableFileError.ts Outdated
Comment thread packages/jest-haste-map/CLAUDE.md Outdated
@pkg-pr-new

pkg-pr-new Bot commented Aug 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

babel-jest

npm i https://pkg.pr.new/babel-jest@16358

babel-plugin-jest-hoist

npm i https://pkg.pr.new/babel-plugin-jest-hoist@16358

babel-preset-jest

npm i https://pkg.pr.new/babel-preset-jest@16358

create-jest

npm i https://pkg.pr.new/create-jest@16358

@jest/diff-sequences

npm i https://pkg.pr.new/@jest/diff-sequences@16358

expect

npm i https://pkg.pr.new/expect@16358

@jest/expect-utils

npm i https://pkg.pr.new/@jest/expect-utils@16358

jest

npm i https://pkg.pr.new/jest@16358

jest-changed-files

npm i https://pkg.pr.new/jest-changed-files@16358

jest-circus

npm i https://pkg.pr.new/jest-circus@16358

jest-cli

npm i https://pkg.pr.new/jest-cli@16358

jest-config

npm i https://pkg.pr.new/jest-config@16358

@jest/console

npm i https://pkg.pr.new/@jest/console@16358

@jest/core

npm i https://pkg.pr.new/@jest/core@16358

@jest/create-cache-key-function

npm i https://pkg.pr.new/@jest/create-cache-key-function@16358

jest-diff

npm i https://pkg.pr.new/jest-diff@16358

jest-docblock

npm i https://pkg.pr.new/jest-docblock@16358

jest-each

npm i https://pkg.pr.new/jest-each@16358

@jest/environment

npm i https://pkg.pr.new/@jest/environment@16358

jest-environment-jsdom

npm i https://pkg.pr.new/jest-environment-jsdom@16358

@jest/environment-jsdom-abstract

npm i https://pkg.pr.new/@jest/environment-jsdom-abstract@16358

jest-environment-node

npm i https://pkg.pr.new/jest-environment-node@16358

@jest/expect

npm i https://pkg.pr.new/@jest/expect@16358

@jest/fake-timers

npm i https://pkg.pr.new/@jest/fake-timers@16358

@jest/get-type

npm i https://pkg.pr.new/@jest/get-type@16358

@jest/globals

npm i https://pkg.pr.new/@jest/globals@16358

jest-haste-map

npm i https://pkg.pr.new/jest-haste-map@16358

jest-jasmine2

npm i https://pkg.pr.new/jest-jasmine2@16358

jest-leak-detector

npm i https://pkg.pr.new/jest-leak-detector@16358

jest-matcher-utils

npm i https://pkg.pr.new/jest-matcher-utils@16358

jest-message-util

npm i https://pkg.pr.new/jest-message-util@16358

jest-mock

npm i https://pkg.pr.new/jest-mock@16358

@jest/pattern

npm i https://pkg.pr.new/@jest/pattern@16358

jest-phabricator

npm i https://pkg.pr.new/jest-phabricator@16358

jest-regex-util

npm i https://pkg.pr.new/jest-regex-util@16358

@jest/reporters

npm i https://pkg.pr.new/@jest/reporters@16358

jest-resolve

npm i https://pkg.pr.new/jest-resolve@16358

jest-resolve-dependencies

npm i https://pkg.pr.new/jest-resolve-dependencies@16358

jest-runner

npm i https://pkg.pr.new/jest-runner@16358

jest-runtime

npm i https://pkg.pr.new/jest-runtime@16358

@jest/schemas

npm i https://pkg.pr.new/@jest/schemas@16358

jest-snapshot

npm i https://pkg.pr.new/jest-snapshot@16358

@jest/snapshot-utils

npm i https://pkg.pr.new/@jest/snapshot-utils@16358

@jest/source-map

npm i https://pkg.pr.new/@jest/source-map@16358

@jest/test-result

npm i https://pkg.pr.new/@jest/test-result@16358

@jest/test-sequencer

npm i https://pkg.pr.new/@jest/test-sequencer@16358

@jest/transform

npm i https://pkg.pr.new/@jest/transform@16358

@jest/types

npm i https://pkg.pr.new/@jest/types@16358

jest-util

npm i https://pkg.pr.new/jest-util@16358

jest-validate

npm i https://pkg.pr.new/jest-validate@16358

jest-watcher

npm i https://pkg.pr.new/jest-watcher@16358

jest-worker

npm i https://pkg.pr.new/jest-worker@16358

pretty-format

npm i https://pkg.pr.new/pretty-format@16358

commit: a618f0f

The added tests covered `EACCES`, `ENOENT` and `EISDIR`, all of which behave
identically before and after the change, so reverting the fix outright left
them green — the `EPERM`-on-Windows path they exist for was never asserted.
Cover both platforms, loading `FileProcessor` through `jest.isolateModules`
because the predicate reads the platform once at module load.

Also fix two claims found in review. The comment cited `nodejs/node#4337`,
which is an HTTP Upgrade change rather than anything about file locking; drop
the reference and describe the behaviour on its own. And `statCache` spares
repeated `lstat` calls only best-effort — the per-root walks run concurrently
and can both miss a path before either caches it, as `walk.ts` already notes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SimenB
SimenB merged commit 1cb03b5 into jestjs:main Aug 17, 2026
105 checks passed
@SimenB
SimenB deleted the haste-map-docs branch August 17, 2026 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

require-changelog If a PR does requires a changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants