jestjs/jest · error · AggregateError

Failed to start watch mode.

Error message

Failed to start watch mode.

What it means

When jest-haste-map starts watch mode, it instantiates one watcher per root and waits for each to emit 'ready' within 240 seconds. If any watcher rejects (emits 'error' during startup) or times out without becoming ready, WatcherDriver.start throws an AggregateError whose message is 'Failed to start watch mode.' The AggregateError's `errors` array contains the per-root rejection reasons.

Solutions

  1. Check the AggregateError's `errors` array for the per-root cause — `catch(e) { console.error(e.errors) }`.
  2. Raise the Linux inotify limit: `echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p`.
  3. Remove or fix any non-existent root directory from the Jest config.
  4. Reinstall native deps (`npm rebuild`) if @parcel/watcher binding is missing.
  5. If using watchman, run `watchman version` and `watchman shutdown-server`, then restart.

Example fix

// before — root doesn't exist
module.exports = { roots: ['<rootDir>/src', '<rootDir>/deleted-pkg'] };
// after
module.exports = { roots: ['<rootDir>/src'] };
Defensive patterns

Strategy: validation

Validate before calling

// Verify roots and inotify limits before starting watch mode (Linux).
import { existsSync } from 'node:fs';
import { readFileSync } from 'node:fs';
function checkWatchPrereqs(roots: string[]) {
  const missing = roots.filter(r => !existsSync(r));
  if (missing.length) throw new Error(`Unwatchable roots: ${missing.join(', ')}`);
  if (process.platform === 'linux') {
    const max = readFileSync('/proc/sys/fs/inotify/max_user_watches', 'utf8').trim();
    if (Number(max) < 524288) {
      console.warn(`inotify max_user_watches=${max} is low; consider raising it.`);
    }
  }
}

Try / catch

try {
  await watcherDriver.start(onChange);
} catch (error) {
  if (error instanceof AggregateError) {
    for (const reason of error.errors) console.error('Root watcher failed:', reason);
  }
  throw error;
}

Prevention

When it happens

Trigger: WatcherDriver.start uses Promise.allSettled over _createWatcher calls (line 71). A watcher's 'error' event fires during startup (onStartupError rejects), or the 240s MAX_WAIT_TIME timeout fires (line 121-126) rejecting with this same message. When any root is rejected, all fulfilled watchers are closed and the AggregateError is thrown at line 82.

Common situations: A root directory does not exist or is not watchable; watchman subscription fails; @parcel/watcher native binding crashes or is missing on the platform; inotify watch limit exhausted on Linux (`fs.inotify.max_user_watches` too low); very large repos where 240s is insufficient for the initial crawl.

Related errors


AI-assisted analysis of jestjs/jest@8e6d128e4a (2026-08-10). Data as JSON: /api/errors/fca864b6669f07a4. Report an issue: GitHub.

Appendix: source

Thrown at packages/jest-haste-map/src/watchers/index.ts:82

  }

  async start(onChange: OnChangeCallback): Promise<void> {
    const Backend: WatcherCtor = this._useWatchman
      ? WatchmanWatcher
      : ParcelWatcher;

    const results = await Promise.allSettled(
      this._roots.map(root => this._createWatcher(Backend, root, onChange)),
    );
    const fulfilled = results
      .filter(r => r.status === 'fulfilled')
      .map(r => (r as PromiseFulfilledResult<IWatcher>).value);
    const rejected = results
      .filter(r => r.status === 'rejected')
      .map(r => (r as PromiseRejectedResult).reason);
    if (rejected.length > 0) {
      await Promise.allSettled(fulfilled.map(w => w.close()));
      throw new AggregateError(rejected, 'Failed to start watch mode.');
    }
    this._watchers = fulfilled;
  }

  async close(): Promise<void> {
    await Promise.all(this._watchers.map(watcher => watcher.close()));
    this._watchers = [];
  }

  private _createWatcher(
    Backend: WatcherCtor,
    root: string,
    onChange: OnChangeCallback,
  ): Promise<IWatcher> {
    const watcher = new Backend(root, {
      console: this._console,
      dot: true,
      glob: this._extensions.map(ext => `**/*.${ext}`),

View on GitHub (pinned to 8e6d128e4a)