jestjs/jest · error · ValidationError

haste.enableSymlinks is incompatible with watchman

Error message

haste.enableSymlinks is incompatible with watchman

What it means

Near the end of normalize (packages/jest-config/src/normalize.ts:1060), Jest validates that `haste.enableSymlinks` is not combined with `watchman: true`. Watchman's file crawling does not follow symlinks the way Jest's haste-map symlink support expects, so enabling both produces incorrect module resolution; Jest throws a ValidationError.

Source

Thrown at packages/jest-config/src/normalize.ts:1061

            return {
              config: watchPlugin[1] || {},
              path: resolveWatchPlugin(newOptions.resolver, {
                filePath: watchPlugin[0],
                requireResolveFunction: requireResolve,
                rootDir: options.rootDir,
              }),
            };
          }
        });
        break;
    }
    // @ts-expect-error: automock is missing in GlobalConfig, so what
    newOptions[key] = value;
    return newOptions;
  }, newOptions);

  if (options.watchman && options.haste?.enableSymlinks) {
    throw new ValidationError(
      'Validation Error',
      'haste.enableSymlinks is incompatible with watchman',
      'Either set haste.enableSymlinks to false or do not use watchman',
    );
  }

  for (const [i, root] of newOptions.roots.entries()) {
    verifyDirectoryExists(root, `roots[${i}]`);
  }

  try {
    // try to resolve windows short paths, ignoring errors (permission errors, mostly)
    newOptions.cwd = tryRealpath(process.cwd());
  } catch {
    // ignored
  }

  newOptions.testSequencer = resolveSequencer(newOptions.resolver, {

View on GitHub (pinned to f49721c78e)

Solutions

  1. Disable watchman: set `watchman: false` (Jest will fall back to its own crawler which supports symlinks).
  2. Or disable symlink support: set `haste: { enableSymlinks: false }` if you do not actually need symlink following.

Example fix

// before
module.exports = {
  watchman: true, // default
  haste: { enableSymlinks: true },
};

// after (option A: keep symlinks, drop watchman)
module.exports = {
  watchman: false,
  haste: { enableSymlinks: true },
};
Defensive patterns

Strategy: validation

Validate before calling

function validateHasteWatchman(cfg: { watchman?: boolean; haste?: { enableSymlinks?: boolean } }): void {
  if (cfg.watchman !== false && cfg.haste?.enableSymlinks) {
    throw new Error('haste.enableSymlinks requires watchman:false');
  }
}

Type guard

const symlinksEnabled = (cfg: any) => Boolean(cfg?.haste?.enableSymlinks);
const watchmanEnabled = (cfg: any) => cfg?.watchman !== false;

Prevention

When it happens

Trigger: Configuring `{ watchman: true, haste: { enableSymlinks: true } }` (watchman is on by default, so merely adding `haste.enableSymlinks: true` is enough to trigger it unless watchman is explicitly disabled).

Common situations: Enabling symlink support for a monorepo that uses symlinks/yarn workspaces/pnpm, while leaving the default watchman crawler enabled.

Related errors


AI-assisted analysis of jestjs/jest@f49721c78e (2026-08-03). Data as JSON: /data/errors/85c7eec12f791c4d.json. Report an issue: GitHub.