jestjs/jest · error · Error

Crawler retry failed: Original error

Error message

Crawler retry failed:
  Original error: ${retryError.message}
  Retry error: ${error.message}

What it means

jest-haste-map's file-system crawler first tries watchman; if that fails it retries once with the node (fdir/find) crawler. This error is the terminal failure: BOTH the watchman crawl and the fallback node crawl raised exceptions. The combined message shows the original watchman error and the retry node-crawler error so you can diagnose both. It means the entire haste-map build aborts — no module map is produced.

Solutions

  1. Run `watchman version` and `watchman shutdown-server` to reset a stale watchman daemon, then rebuild.
  2. Verify every path in Jest's `roots` / `moduleDirectories` / `testPathIgnorePatterns` resolves and is readable: `ls -la <root>` for each root.
  3. If watchman is the culprit, set `haste: { forceNodeFilesystemAPI: true }` or pass `useWatchman: false` to skip watchman entirely.
  4. Check the two nested messages (Original error / Retry error) for ENOENT/EACCES codes and fix the named path's permissions or remove it from roots.

Example fix

// before
module.exports = {
  roots: ['<rootDir>/src', '<rootDir>/packages/legacy'],
};
// after — legacy path no longer exists; removed it
module.exports = {
  roots: ['<rootDir>/src'],
};
Defensive patterns

Strategy: validation

Validate before calling

// Before building, verify every root exists and is readable.
import { accessSync, constants } from 'node:fs';
function validateRoots(roots: string[]): void {
  for (const root of roots) {
    try {
      accessSync(root, constants.R_OK);
    } catch {
      throw new Error(`jest-haste-map root is not readable: ${root}`);
    }
  }
}
// Call before HasteMap.create(...)
validateRoots(config.roots);

Type guard

function areRootsAccessible(roots: string[]): boolean {
  return roots.every(root => {
    try { accessSync(root, constants.R_OK); return true; }
    catch { return false; }
  });
}

Try / catch

try {
  await hasteMap.build();
} catch (error) {
  if (error.message.startsWith('Crawler retry failed')) {
    // Inspect error.message for both Original and Retry errors,
    // then either fix paths or disable watchman.
    console.error('Crawl failed for both backends. Root cause:\n', error.message);
  }
  throw error;
}

Prevention

When it happens

Trigger: crawl() catches the watchmanCrawl error, enters the retry path (crawlFn === watchmanCrawl), calls nodeCrawl(crawlerOptions), and nodeCrawl also rejects — e.g. due to EACCES/ENOENT on a root directory, a corrupt filesystem entry, or an unreadable file during the fdir walk. Any exception from nodeCrawl is caught and re-thrown as this combined Error.

Common situations: Watchman is installed but misconfigured or its socket is stale, AND a root directory the project points at does not exist or has restrictive permissions (CI containers, Docker mounts, monorepo path typos). Also seen after a branch switch leaves dangling paths referenced by the Jest config roots, or when a symlink loop exists and forceNodeFilesystemAPI is off.

Related errors


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

Appendix: source

Thrown at packages/jest-haste-map/src/crawlers/index.ts:30

export async function crawl(
  crawlerOptions: CrawlerOptions,
  useWatchman: boolean,
  console: Console,
): ReturnType<typeof nodeCrawl> {
  const crawlFn = useWatchman ? watchmanCrawl : nodeCrawl;

  const retry = (retryError: Error) => {
    if (crawlFn === watchmanCrawl) {
      console.warn(
        'jest-haste-map: Watchman crawl failed. Retrying once with node ' +
          'crawler.\n' +
          "  Usually this happens when watchman isn't running. Create an " +
          "empty `.watchmanconfig` file in your project's root folder or " +
          'initialize a git or hg repository in your project.\n' +
          `  ${retryError}`,
      );
      return nodeCrawl(crawlerOptions).catch(error => {
        throw new Error(
          'Crawler retry failed:\n' +
            `  Original error: ${retryError.message}\n` +
            `  Retry error: ${error.message}\n`,
        );
      });
    }

    throw retryError;
  };

  try {
    return await crawlFn(crawlerOptions);
  } catch (error: any) {
    return retry(error);
  }
}

View on GitHub (pinned to 8e6d128e4a)